Generics in Go: Draft Proposal Review

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

This is a terrific talk! For those who aren't sure what generics even are or what problems they might solve in Go, I wrote a tutorial 'as though' generics already exist: https://bitfieldconsulting.com/golang/generics

👍︎︎ 7 👤︎︎ u/bitfieldconsulting 📅︎︎ Oct 17 2020 🗫︎ replies

Nice video.

I just wish we do not have any predeclared type constraints like `comparable` & `any`
The draft talks of a possible new `package constraints`; If that does happen, I would expect all constraints to be in that package (including `comparable` and `any`).
I know `any` is an alias for `interface{}` but I would want that alias to be defined inside `package constraints`

👍︎︎ 3 👤︎︎ u/komuW 📅︎︎ Oct 17 2020 🗫︎ replies

I'm interested in what you guys think about the generics proposal

👍︎︎ 2 👤︎︎ u/duckenthusiast17 📅︎︎ Oct 17 2020 🗫︎ replies

This is an outstanding lecture.

I highly recommended this to anyone still unsure about whether the proposed generic syntax fits in to the Go concept. I think it does and the video encourages me to continue thinking that.

👍︎︎ 1 👤︎︎ u/JetSetIlly 📅︎︎ Oct 17 2020 🗫︎ replies
Captions
before i get started please subscribe to this channel so you don't miss out on any new material that i'm going to be producing in the future and that helps us grow the channel grow more content and help people like you [Music] well i appreciate everybody being here this evening i know it's late for i think about what 7 p.m where you are right now so i appreciate that this talk is gonna take a little bit over an hour i can't get it any shorter and present enough of the draft design where you have a really good strong understanding of where this is going so this talk is trying to present to you the current draft syntax proposal where we are today right now it seems like the language team and the community are pretty happy with where we're at and which i'm going to show you but you're going to have an opportunity also to participate in that conversation actually eric i'm going to send some links to you over slack can you drop that in the chat for everybody that would be appreciated [Music] these three right there a couple of ground rules here as we get started eric's dropping some links into the chat window i'm not going to really be able to take questions during the talk so what i'd like is everybody really participating in that chat area in zoom have as much many conversations as you want uh and help each other out through the through the course of the talk once the talk is over uh we're a small group here i'd like to get feedback from everybody so we'll open it up to give everybody a turn to talk for two or three minutes um after the talk okay so and a lot of times i'm answering questions by the time i'm through as well but ask me if you ask questions on chat maybe somebody has an answer already for you i'm going to try to present the draft syntax that's all we have right now we've been exploring and experimenting and prototyping with what a syntax could look like for generics it's important that you understand that there is no compiler implementation of this as of yet though um the go team has started some preliminary work on that i think it was this week so they're feeling more and more confident that the syntax i'm going to present you is probably where we're going to end up the other thing here is when do when we when could we expect if at all a version of this in the compiler the language team is telling me that if everything goes perfect between now and august release of next year which will be version 117 we could potentially see a compiler implementation that would be the earliest version 117 august though if i was a betting person i would say the 118 february release of 22. but you never know see how it goes there's more community discussions that have to take place now i asked eric to drop three links into that chat the first link is where the training material is i'm going to be showing you that in a second the second link is a link to the draft document and the third link is a link that points to a form to be able to provide anonymous feedback to the go team that is working on this so that's your opportunity to participate in the conversation without getting it into that public you know the the mailing list uh and issues and things like that so i hope you take advantage of that okay um i think with all that said it's on my side it's 210 i think that means it's uh 710 on your side eric switch over to my machine here i'm going to switch over to the repo i've got the fonts a little higher so we can see things a little bit better this is the go training repo i know it's a tad blurry zoom never gives me high def video for some reason but um you can see here there are 13 different examples we cannot go through 13 of these we just don't have the time i'm only going to get through the first five if i get through the first five you will have seen a a really good maybe 80 85 percent of what's being discussed in the draft document the rest of these examples i'll leave up to you to look at on your own there's a readme in the readme you'll find a link here that will also take you to the draft document you can see it's been updated as early as three days ago now um you don't necessarily have to read this the whole point of this talk is so i can kind of go through what's here without you having to sit and read it if you have read it that's great there's also a go to go playground that lets you explore and experiment with the syntax this is completely up to date we won't be using this i'll be running it locally but throughout the course of the talk if you've got some code that you want to share with me at the end of the talk you can put it in here and hit that share button and give me a link and then we can kind of discuss it as well so the go to go playground is awesome again just remember that this go to go is not a compiler implementation but a transpiler tool that will convert the new syntax into existing syntax so it can build and run now i'm not going to read this list but this is the things that are currently being explored and prototyped as a first release of generics it doesn't mean that this won't make it and to go in the following releases uh 18 19 20 21. it just means that right now this is what's being focused on as an initial implementation and for syntax so you can read that on your own i don't need to read it if you're wondering if any of these things are going to be in the first release the answer right now is no i don't expect any of this like meta programming to get into the first release of generics for go it doesn't mean again that it may not make it in but i won't be showing you anything related to this list right now because it's just not being worked on there are some extra links in here for reading if you want to go deeper i'm writing different blog posts for the different examples and if you do want to play with the go to go tooling locally there is instructions here on how to do that and that's exactly what i'm doing right now if i go over to my terminal window here type go version um you'll see that i'm not running 115 2 i'm running a version back from source on the 15th which is uh i think about nine days ago so um that's where we are right there okay with all that being said it's now a quarter after the hour i expect to be done at around the same time next hour a lot to cover um so we're going to get to it and i'm going to do that using uh the vs code environment that i have and we're going to start with this very first example here let's see if i can work with that's a little too big for me i want to go a little lower i think that's okay on the resolution if at any time during the talk you can't hear me you can't see something something seems to be technically going wrong place uh just type something in the chat window to let us know i can see the chat i can see what you see and eric is available to help us with any issues there okay let's learn the new syntax being proposed for generics and also let's understand why we even need a new syntax i think that's that is as important as anything else so on line 13 i'm going to start with what i call a concrete function i call it a concrete function because this is a function that's asking for data based on what it is and anytime we have a function asking for data based on what it is we're really limited to just asking for that one type of data that one type of concrete data and this print numbers function is asking for just a slice of integers a collection of integers in go now if you look at the code we're writing for this print numbers function it's five lines of code we're in line 14 we print a header on line 18 we do a carriage return on line feed but in the middle of that we're doing a linear traversal using our language mechanics and syntax the four range mechanics there they are where we get to iterate over each number in the collection and then we from print that number and we can see them all five lines of code there it is to display individually each number in that collection now i'm going to make a big bold statement here and say that i believe that almost all go developers even those that are only maybe a week or so into the language can read and comprehend the code i just wrote for print numbers i think it's one of the things that we love about go that go is really not trying to be novel in terms of syntax and language mechanics that if you're a software developer maybe if you've never even seen go before but if you're a software developer you can read those five lines of code and understand it and what i want to focus on in this talk and what i want the community to focus on here is not will generics allow us to write code that can perform faster than what we have today but can we write code that will give us higher levels of readability and comprehension for the average developers that are working on our team because if the answer is no then i want nothing to do with generics this has to give us the ability to write code with that same or higher levels of readability comprehension with with the ability to also reduce maintenance costs right because that's that's going to be a big thing i'm going to focus on all of those as we go now i've got this print numbers function i've i've made an argument that everybody can read this uh fairly quickly even if you don't know ghost syntax it's kind of obvious but if i wanted to now do the same thing for a collection of strings because these are concrete functions i now have to write an entirely new function that's what you see on line 21 right you see me writing a new function called print strings that now takes a collection or slice of string and we can both we can all look at the five lines of code here between lines 22 and 26 and say bill that's essentially the same code the only difference is the variables are changing to be in context with the data type and i've changed the header but mechanically it's the same code right now if i wanted to do this with floats what's going to happen i got to write another function now i could make the argument that writing another function for each of these can add some value because if this function print numbers has a high level of readability and comprehension then so does print strings and so would print float and print user and that's going to help a lot with maintainability right well i think the the cost of that the cost is going to be what we're going to have a lot of functions and a lot of code that we have to maintain and potentially if we find a bug in one of these functions we're probably going to find the same bug in all the others and so even though our readability and our comprehension of this code is high the maintainability cost here is is high too right the cost is high it's harder and so when i think about the problem that i have in front of me thinking about the current language that we work with today our language syntax and mechanics for 115 i asked myself well is there a way to write this code where we can consolidate it all in one function to help reduce the complexity and maintainability but still maintain that high level of readability and when i think about that i do come up with an answer right here which on line 35 is my print assert function and the idea of this function is a way of consolidating all the at least those two other functions into one function to help with the maintainability right i have to maintain two three four functions i maintain one but to get this to work i can't be using a concrete function anymore i need essentially to write a polymorphic function what do i mean by that a polymorphic function doesn't ask for data based on what it is a polymorphic function asks for data based on what it can do that's the idea that allows us to pass different types of concrete data through the function based on the fact that all of that data can do the same thing now in this particular case i don't particularly care what the data can do and so you see the use of the empty interface now the problem with the empty interface is it does allow us to write a polymorphic function but it doesn't describe anything about the data we're really blind but in this particular case this is the only way to do it because i want to be able to pass a slice of int or a slice of string and that empty interface allows me to do that but once we come into the function v the interface value is abstracting the concrete data and since there's no behavior behind it we need to inspect what's inside the interface now there's a couple of mechanics to do that and go and what you're seeing here is the use on line 37 of the type assertion a type assertions let us at runtime inspect what concrete data is sitting inside of that interface which is decoupling us from the concrete data and you're seeing also the use of a generic type assertion using the keyword type and the switch statement so this is a generic type assertion as well where we're going to use each case to determine conditional logic for what is inside of v and if there is a slice of integer inside of v we'll execute that code if not we'll execute this code and so we've consolidated this but a couple of things here right this really isn't a generic function because if i pass a slice of anything else through we're not going to see an output we can only output here what we code and two the code on lines 39 through 41 again mechanically is the same as 43 through 45. the only thing we're changing is the variable name just to have some context so we do maintain i would argue a high level of readability and comprehension because we're using our language syntax and mechanics we have gained some maintainability because we've got just one function but there's still a an ounce of code duplication here that you know isn't necessarily great so then i ask myself the following question well in the current version of go right now 115 is there a way to write this print function in a generic way where i don't have to worry about multiple implementations i can write just one implementation with one function and then i'm set and the funny thing is the answer to that question actually is yes here it is right there on line 56 i call this function print reflect and once again we use the empty interface to be able to accept concrete data based on what it can do in this case i don't really care what it can do so we could pass any sort of concrete data we want but if you just stare at the eight lines of code that i wrote there i think you're going to start to realize that you don't see a lot in terms of language syntax or mechanics and that's because to write this generic version of print i have to use an api i have to use the reflect package from the standard library this implementation is api based not necessarily language syntax based and you can see what's going to happen here on line 58 we do our value of call so we can get a reflect value that gives us the ability to inspect at run time what's going on with the concrete data stored inside of v and then i can use the method set of val in many different ways i can ask well what kind of data stored inside of v because if it's not a slice we're done the whole point here is to print a collection of values i can do this at runtime and there it is now i can't use my language mechanics in syntax right but i do have to do the linear traversal so what you see me doing on line 62 is incrementing a local variable i through length i can't use the built-in function len but i can use the method and then i can go ahead and do my linear traversal again using another method called index an interface gets me back to the data and then i can just display it all on my print this is truly a generic function this can print a collection of ins or floats or users or anything and really it's capable of doing that because the print function from the front package is also using reflection underneath that print function uses reflection to determine a default output for any sort of data value that we pass into it and so here we see the use of reflection in two cases one in that print and two in our ability to inspect at runtime the data and we see the use of a polymorphic function to accept any concrete data we need in this case and it doesn't in this case and it doesn't have to behave but i can't make the same argument i made before these eight lines of code are not as readable they're not going to be as comprehensive as what we saw before the average developer on our team and anybody brand new to go isn't going to necessarily understand this code just by looking at it you need to understand the api and so though maybe we have some maintainability gain here because it's one function and eight lines of code to do it all we are losing readability right we are we're not gonna this code isn't gonna be as readable to as many go developers as the other version and so now i get to this final question is there value in being able to write code with our language syntax and mechanics and still have the ability to just maintain one function and i think the answer is yes and can this new generic syntax give us that and i think the answer is yes as well so what i want to do now is show you based on the draft syntax that we're currently exploring what a generic version of print could look like outside the use of the reflect package now i want you to just focus here on lines 82 through 86 to begin with because if you notice it is the same exact five lines of code that i started this talk with it's the same five lines of code we used when we were working with a slice event or slice of string and it would be the same five lines of code if i wanted to work with floats and users we're back to using language syntax and mechanics and this is because we're back to really writing a concrete function it's no longer a polymorphic function we're writing a concrete function well it's interesting because this is a concrete function where we want to be able to at compile time where we want to be able to determine later what the actual concrete type is and sometimes that's called paramedic right para parametric sorry polymorphism it's a different kind of polymorphism it's at the compiler level instead of what we were seeing in our traditional polymorphic functions right um say at runtime so i want to explore here a little bit of the new syntax because to get this to work we needed some new syntax but to do that i'm going to remove the new syntax first so we can look at this function as if it's just a regular go function that we're used to writing today now you know what i'm trying to do here right i'm trying to say that i want to write a function named print that accepts a slice of t but not really t but a type to be determined later the whole point is i want t to be substituted for something later but write the code now as if t is something concrete like an int or float or user the problem with this syntax that we have here that i've written is that this syntax assumes that somebody somewhere before we hit the build button is going to be declaring t this syntax that we have today assumes that t will be declared and will be identified before we hit build there it is there's t life is good and this is really the quite the opposite of what we want right the whole point of what we're trying to do here is say that t isn't going to be declared before i hit the build button and that this t needs to be identified later potentially at compile time and this is why we need a new syntax because what we've got to do is tell the compiler don't look for t at least not right now we want t to be identified as a generic type ah so we need a syntax that tells the compiler that t will be a generic type how do we do that well in the current draft the syntax that seems to be holding pretty strong is to use a set of square brackets to define a list of generic type identifiers a list it doesn't just have to be one i mean it could be many but this is where we're going to declare that t is a generic type but in order to make sure that the compiler never confuses this with an array anywhere within the scope of the syntax the spec says that all generic types must have declared with them a constraint and when there is going to be no constraint associated with the type we are to use the pre-declared identifier any which to me represents anything in other words what we're saying now is t is a generic type to be determined later and it can be substituted for anything that's what we're doing i'm going to the last two examples in this talk i will talk about constraints for now with what i'm going to be showing you when i apply the constraint it will be any representing that t can be substituted for thing and now we have the new syntax and the purpose behind it is to tell the compiler that t is not some concrete type it should expect to see being declared somewhere in some file but t represents a generic type to be determined later and now since t represents a concrete type we get to write code as if it is and we get back to our language syntax and mechanics and now we're back to five lines of code that everybody can read and comprehend and understand and we gain the benefit of maintenance because this is the only implementation we need there it is beautiful now let's run this code here and so in main i'm going to construct a slice of integers on line 92 i'm going to construct that slice of strings on line 98 and then pass that slice across the four functions that can accept it and pass that slice of strings across those four functions and if i come into my terminal here and we change into the first function example we can run this code now remember there is no compiler implementation yet so i can't just say go run doesn't exist yet but i can call out to the go to go transpiler since it is a tool that's accessible in this version of go that i have from the front end and then i can say run and provide it the go to source code file what will happen is the go to go transpiler will convert this into a go program that is capable of being built and run with a bunch of interesting support functions underneath but if it enter on this we now see the output and we see here that our reflect and our generic and all the other functions have the same output and what is the big win here what are we really trying to accomplish here with the readability higher levels of code comprehension and maintainability well this right isn't this the big win the ability to say i'm going to have a set of floats which are going to be a float 64. and just apply a couple decimal points here like this and then be able to take our two generic versions of this code remember reflect is as generic as print it's just this is api based this is language syntax based so i should be able to build and run this code let's see there it is and so we now see without an extra implementation that i'm now able to display floating point numbers using my reflect and my generic functions okay now there's a couple more things here that i do have to talk about a big part of the compiler implementation is to make this usable and to make it usable what's going to have to happen is that the compiler when it's capable of doing so wants to and really should need to infer what type t is going to be you heard me say that t is to be determined later sometimes t can be determined at compile time if it can infer that type based on the call site or the construction site what we're seeing right now is the compiler figuring out what t is at the call site because of the way the code is constructed and the fact that i'm passing in a slice event a slice of float and a slice of string so when t is being used as an argument for input this lends itself to the compiler being able to infer the type this is beautiful so if i pass in numbers and the compiler sees this as a slice event then the compiler can assume that we're substituting t for an int and then compile and build the code being able to infer the type at compile time is beautiful but what if t wasn't being used as an input argument it's just being used somewhere within the implementation of the function now there's nothing for the compiler to leverage to infer so how do we determine what t is later if it can't be done necessarily at compile time well we have the ability to pass the type information in using the same square bracket syntax in other words these parentheses represent data we can pass in these parentheses also represent type information that we can pass in though we would prefer not to have to do that if the compiler can infer the type of compile time so we're not just declaring right a generic type to be determined at compile time this list can also be used as a way of passing that information in so i can say here float 64. i can explicitly pass into here the compiler doesn't have to worry about trying to infer what type t is and then it can guarantee that it matches the data input on that call site so i want you to understand that when i say to determine later type t to be determined later that could mean that it determined that compile time by trying to infer the type based on say data input or it might mean that we have to explicitly tell the compiler what t is going to be because it can't infer it by itself but in the cases where it can it's beautiful because calling a generic function looks and feels no different than calling any other function okay great now we've looked at generic functions here and that's great but you know one of the things that i teach that i hold true to is that go is a data oriented design programming language in other words with data oriented design says is every problem that you solve is a data problem every problem you solve is a data transformation problem there's some input you transform it there's some output and so if you don't understand the data you don't understand the problem if you don't understand the problem why are you writing any code i mean rob pike and i'm paraphrasing said that if you're able to define your data structures the right way the algorithms are self-evident in other words we start we start with the data we start with our data models we start with our data structures in the concrete and other things should be discoverable the algorithms should be self-evident so we're going to be defining lots of user-defined types in the software that we write and generics are going to play a role in that now there's two ways of defining a user-defined type in go we can define a new type based on an underlying type or we can define a type based on a struct type so i want to present both of these to you let's start with an underlying type okay let's get to the beginning of this code so you see here on line 15 i'm declaring a type named vector int that has an underlying type of a slice event which really means that this vector int is a slice right like it's its data model is a slice there on line 24 i'm defining a new type named vector string whose underlying type is a slice of string that's what it is it's a slice of string now if i wanted a vector of floats as you know i'm going to have to define another type but to make this example more interesting on line 17 and 26 i added a method to this type called last what last does is return the last value in the vector which is the value at the highest index position now the last implementation for vector int has to return an int because that's the kind of data we're storing the last implementation for vector string has to return a string because that's the kind of data that we're storing and you can see that the four lines of code here really match the four lines of code here and if we walk through it what we'll see is that we're checking to see if the vector is empty and if it is we return an error and then the zero value for an int which is the idiom in the guideline that we follow and teach in the community if you're returning an error then return the zero value construction for all those other types we tend to do that directly on the return if it's not empty then we return the value the integer value the highest index position and nil which is the zero value for the error interface now the implementation of last is identical except for one thing the zero value for a string is not zero it's an empty string but it's okay we hard code that right there on the return and life is good okay so i've got these two implementations of a vector and again if i wanted one that works with floats or users i'd have another implementation we're back to the high level of readability but the lower level of maintainability now the question is can i do this in a generic way today and go i can on line 40 i can change the vector out to have an underlying type that is a slice of the empty interface we're back to polymorphism again right but polymorphic behavior that really describes no behavior so we're kind of blind again and you can see the implementation of last is the same four lines of code where we're returning nil for the zero value of an interface because that's what that is now the same four lines of code apply we could argue that the readability in this case really didn't change but i'm going to make an argument that the usability has changed there's one big semantic change between this vector and the others that i showed before the others that i showed before were restricted to one type of data so when we called last we knew what we were getting back but we can intermix different types of data in this vector which means that when i call last i really don't know what i'm getting back i'm blind i'm getting back an interface value that has a concrete value in it of some unknown type which means from a usability perspective i'm back to having to do type assertions or reflection to know more to then write whatever business logic i need to write so there are some semantic and usability changes here what i really want is a generic implementation of a vector that is restricted to a single type of data so i can get my readability and my maintainability and in this case my usability right and so here we are again with our generic implementation of vector throughout this talk i'm going to remove parts of the new syntax because i don't know about you but my brain is more wired into looking at code without it and then applying it in makes it easier look you know what i'm trying to do on line 56 right i'm trying to tell the compiler i would like to declare a vector that has an underlying type of a slice of some type t to be determined later that's the whole point not actually t but some type t to be determined later as soon as i say some type t to be determined later what do we know well that's a generic type what do we know we need some square brackets to help declare the generic type identifier and we need the constraint of any to again represent that there is no constraint on what t can be substituted for okay great so now i have my vector type which is leveraging a generic type identifier to declare what the underlying type is a t some type t to be determined later now let's take a look at the last method because since this is a method it's going to be bound to the type through the receiver since this is a method bound to the type through the receiver we'll never see another type list like we saw with a generic function because that syntax is already being applied to here on the declaration of the vector but look at the receiver you see that i'm using value semantics this is a value receiver there's no pointer here it is a value of type vector but remember vector by itself doesn't mean anything because we've got to tell the compiler at any point in any sort of construction syntax or declaration syntax the fact that what the underlying type is going to be and so anytime we use the identifier vector it's never going to be enough without the use of the square brackets and some type information but since we don't know what the type information is here because we're declaring a method we're going to use t as well and we're saying that this method is bound to a vector of some type t that ends up being determined later whether a compile time or explicitly what's also nice is that we're returning a value of type t as well to be compliant with the way the api works for storing integers we've got to return into restoring strings we've got to return strings users users now what i love about this is we're back to those same four lines of code right the same language syntax and mechanics that we've been using and this implementation is bound to a single type we get readability we get better comprehension we get maintain ability there we are now let's follow the code here we're going to check to see if it's empty if it's not we return the value of type t in the largest index position that that is there and nil for an error but if it is empty if it is empty then we return the error and now we have a problem because i want to return the zero value of t in order to do that we need a universal way of being able to construct a value of t to its zero value state everybody's first initial reaction to this is to do this is to construct t using empty literal syntax most of the words i'm saying empty literal syntax why not just use empty literal syntax to construct a value of type t to its zero value state on the return bill well look if t was substituted for a struct type named user that would work it would it would work but t was substituted to be an int that syntax isn't valid doesn't even make sense we can't construct an int using empty literal construction no i mean it doesn't work and so not all types not all types work with that syntax so we need a construction syntax that works for all types now the language has two of them believe it or not the first one and the one i prefer is what i'm showing you on line 59 and that's using the keyword var which is why in all of my classes i always teach use var not empty literal construction for zero value because it is a universal way of constructing any type t to its zero value state and just remember as a side note if you use empty literal construction on a slice you don't get zero value you get an empty slice and so what i like about this solution is regardless of what t is whether it's a user whether it's a string whether it's an int whether it's a slice it all results in the same things zero value construction and then what we can do is use that variable on the return which i think gives us higher levels of readability in terms of we're returning the zero value for that type t this is the solution i like now there is a solution that would eliminate that line of code the other universal way to construct any type to a zero value state is using the built-in function new new constructs a value to its zero value state however nu uses pointer semantics and will return the address of t and so to be compliant with the type information we would have to dereference that pointer back to the value that the pointer points to which to me has lots of data semantic issues if you take any of my classes but more importantly i don't believe that that code is as readable and will be as comprehensive right comprehendable to as many go developers as the other syntax we did get rid of a line of code i potentially made something easier to do but did i make it easier to understand my argument is no more go developers especially those new to the language will understand this syntax much more than they will understand the dereferencing of a call to new they both work they both result in the same thing but i think data semantic wise i think reading-wise comprehensing-wise this is a better solution okay so we now have our vector defined here it is and i want to see how this now works so if i go into main i start with the construction of our vector int and vector string which we're bound to a specific underlying type but remember the beauty of these vectors was when i call last i know i get an integer which means i can provide some sort of extra logic without any sort of usability issues i know i'm getting an int is it negative on this call to last i know i'm getting a string is it valid i can do that the problem is if i need to apply the same sort of logic and we work with the interface using the uh the vector using the empty interface i have my usability back because look there's nothing stopping me from storing different types of data in this one vector now when i call last i don't immediately know what type of data i have i have to go back to reflection or in this case a generic type assertion and then and only then can i apply the right logic so i feel like this solution lends itself to misuse and fraud and usability issues which is why i love love the idea of our generic vector so on line 107 you see me constructing a vector i'm specifying that t will be an integer and then i'm using literal construction to initialize that vector at construction time what's beautiful is since t is an int we know that last returns an int and i'm back to being able to do any sort of processing but using the same vector declaration i can now specify that t is a string and using that literal construction puts some strings into the vector and then we know last is returning a string and there we are and if i were to just build and run this code for a second we see down here that that same vector implementation is now being used for ins and strings and what's the big win again what is the big win right the big win is that from an application standpoint i can do this right and now just start working with any other type that i want maybe we'll just call this a float we know that this now returns a float right which means i can even do negative processing if i want it's a negative float and what's also cool is i can use the specific verb for float inside that print statement because i know what type of data i'm working with and if i did everything right we're now working with a vector of type float now one question you might be asking is this separate this for a second bill why are we why are you passing the integer information explicitly when you're using literal construction can't the compiler figure out based on the values that are part of the literal construction that this vector should be working with an integer well the answer to that question is actually yes it can when then bill why is it not working you may run into situations right now where certain things are supported by the draft but they're just not implemented yet and probably won't be implemented because why spend time on something that isn't that critical in terms of getting to a design that will eventually get into the compiler so in a compiler implementation this will work absolutely the compiler will have enough information to be able to determine the type just like this it would have enough information i'm just letting you know that right now the current go to go tooling isn't supporting it because it hasn't been implemented for no other reason then there are more important things to be spending time on but if you were to be constructing a vector to its zero value state you'd have no choice but to explicitly pass it so again anytime i talk about t to be determined later if the if it can be determined through if the compiler can infer the type then t can be determined at compile time if the compiler can infer the type then t has to be determined at the construction or the call site by explicitly passing that information okay brilliant so we've seen now the use of this syntax as it relates to a user-defined type leveraging an underlying type you got to see the implementation of a method where we needed to be able to return a value of that generic type t and we focused a little bit on options you have for doing zero value construction now i don't know about you but probably in the seven years that i've been coding in go i haven't declared a lot of user-defined types using an underlying type i haven't done that that much but struct types are very very very common so i want to share with you how we could leverage generics with a struct type and then this plays into the idea that we can have fields in a struct that are based on that generic type t so if you look at line 16 once again i want to define a node for a linked list these struct types are going to be really valuable for these what they call these container types right these link lists and trees and hash maps and all that other good stuff that we have so once again the idea is that i want to define a node that can store a value of some type t now again since t needs to be a generic type you're going to see me apply the square bracket to define this as a generic type and again there is no constraint on what t can be substituted for so we're going to say it can be anything and now we can define data to be a field of some type t to be determined later now this is a linked list so and it's a double linked list so every node can point to the next node or the previous node that's going to be pointer semantics but it has to be a node of the same type t so just like you saw with the receiver anytime we're going to reference node the type we've got to reference it with the type information for the generic type identifier in this case we don't know what it is just yet but we do know that t is going to be the substitution for it and so what we're going to say is that next and previous point to another node of the same type t there it is now if you look on line 22 we have a list and that list contains the first and last pointers to our list and once again because we've got to have pointers to a node it needs to be a node of some type t so we go ahead and declare that generic type list again saying that t is a generic type for this type it can be anything and then we can apply that to our first and last nodes now to make this more interesting there's a method named add now again it's bound to the list type this time using pointer semantics once again you can't construct a list without knowing what t is so we now use that square brackets and we define add to be able to accept a value of type t and then return a node but again it can't just be a node it's got to be a node in this case of the same type t that we're using to construct the list which is the same type t we're using to pass data let's look at the implementation of add because again what generics is trying to give us is better readability is again higher levels of comprehension but also the maintainability by being able to have just one implementation of something all three of these things really need to come together for generics to be i think a viable solution now if you look at the basically the 11 codes i'm sorry the 11 lines of code that i wrote here you can see here that these lines of code don't change regardless of regardless of what the data is that we're storing in a node this is what makes these container types so miserable to have to have multiple in different implementations for different data types because the bulk of the code is the same right it's the same the only thing that's changing is the construction of a node because the construction of a node has to be of some type t that is being determined later so we can assign that data that type t to the right field and we can have pointers of a node of that same type t to the previous field right this is the big win this is the main obtainability wind that we're going to get so there it is we see an add method now let's just play with this for a couple of seconds on line 46 i'm declaring a type named user that's the data that we're going to be storing in our list and then on line 55 you see zero value construction var of a list where we have to explicitly tell the compiler that t will be of type user because there's nothing that can be inferred here to be determined later is being explicitly provided but because we are using user for type t add now accepts values of type user and data will be values of type user but remember we can substitute t for any type and any data semantic so here we're constructing a list also set to a zero value state but instead of user values we're saying pointers to user addresses of type user which means add now only accepts addresses of type user and data will be an address of type user and we can see that if we go ahead and run the example for three you see that font is telling us ampersand or no ampersand pretty cool and there's some intuitiveness here around the api because if i construct a list where t is of type user then everything is going to be value semantic based if i pass pointer semantics here then everything is pointer semantic based now one thing i just want to make you aware of is that you may have situations where you don't want t or t in the implementation can't be using value semantics maybe the data can't be copied and that becomes a dangerous situation there's nothing stopping you from going into your implementation and forcing pointer semantics say on that data field or forcing pointer semantics on the input there's nothing stopping you from doing that you may even have to when again a value can't be copied now look at what happens though when i do that to this implementation it could be potentially confusing to the user now the add method is asking for pointer of type user now it's asking for a pointer to a pointer of type user makes sense right because in the first case when we pass user here this becomes user it's not this anymore it's this and when we pass this now this became that same thing on this side so i want you to be aware from a usability standpoint that if both value and pointer semantics are safe to use in an implementation then don't play with t let the user define t for what they need it to be what data semantic they want to work in and then the rest of the api follows suit but if you do know that this is dangerous or if they specify user and it's dangerous then yeah you're going to have to go in and you're going to have to try to be very clear that your api isn't matching what you're using for t during construction someone should be aware of that okay aware of that that's tripped me up in some some cases while i was learning okay this is brilliant it is 307 i promised i'd be done at around 315 i'm probably about seven minutes behind i hope that's okay with everyone but there's two more examples that i need to share to give you again that 80 85 of what the draft is really trying to provide and these last two examples are very very important because they focus on another aspect of this that's critically important and that's constraint up to this point these three examples that i've provided t was able to be substituted for anything because there was no real constraint on the implementation of code against t but that's not necessarily realistic there are going to be times where t has to be constrained because the implementation of a function let's say won't support all types and i want to share two examples of that with you before i'm done here with this talk now there's two types of constraints that are applied the first one and the one i'm going to talk about here is behavioral constraint let's start with that so i want you to look on line 13 and 22 you see i'm defining user defined types and in each one of these cases for my user or my customer i've added a method named string that returns a string what these methods essentially do is stringify their related entity line 18 we're stringifying a user on line 27 we're stringifying a customer i'm using a json syntax but why would i ever want to do this maybe i want to log these particular data values and by being able to stringify them i can control how they are written to a log now to make this example a little bit more interesting i've written these string of five functions that can do stringification in a bulk so the first one says stringify users right it's a concrete function it accepts a slice of user and that's it and then it returns a slice of string where we can match the same index position and have the stringification and you can see the five lines of code we we construct the slice of string for the return we can use the built-in function lan to determine the length we then go ahead and really the capacity sorry we go ahead and we do a linear traversal each you each user at a time using append to add a new user to the new slice and the key here is that we have to call string because string has the intelligence that we built in to do the stringification then we can return it and those five lines of code are essentially the same five lines of code you see first stringify customer we've just changed the variable names and that's it it's the same five lines of code it's the same problem that we started with right and the thing here is that that string method is the key to all of it now very quickly i'm not going to break this down we've done it already there it is again using the empty interface and type assertions i could consolidate this stuff but once again the lines of code are pretty much the same right we're not gaining a lot of value around this consolidation this is interesting though with 13 lines of code guess what i'm able to do yep i'm able to write a generic version of my stringify function using the reflect package but we just went from five lines of code to 13 we walked away from our language syntax and mechanics to an api and i'm sorry but every time i look at this code and i wrote it it's not obvious to me just by staring at it i have to break it down every single time right empty interface to accept any data of any type reflect over it is it a slice if not return nil construct the new slice of string to the right capacity using the built-in method len not the built-in function but the method len again we can't use four range mechanics increment i over length now this is cool for every value that we index over i can ask a question at runtime does that value have a method named string awesome now if it doesn't i'm done can't do much but if it does i can call it and then we can take that value and get it into the append call so guess what i'm able to actually through reflection execute a method at runtime if it exists pretty cool but let's agree this code is not easy to read you know there's a higher bar of entry in comprehending what this does we do have a single function in terms of maintenance but i mean even maintaining this code we could easily break something if we're not careful so let's go look at generics because i think generics gives us everything we want here and i'm going to pull this out again and start from where we're at so here it is there's that string of five function it's going to be our generic function once again you can see that this function is asking for a slice of some type t remember generic functions are really concrete functions and we're asking for a slice of some type t but not a t that's going to exist and be declared but a t that represents a generic type and in this case like i've been doing all during the talk i'll just say any a generic type t to be determined later that at least right now can be anything and here are the same five lines of code that i wrote before that we started with that have the higher level of readability and comprehension because we're using language syntax and semantics now i do have a main application here that will call into it i'm not it's not important what's important is i want to go ahead and build and run this code notice i have a compiler message on line 114 it's talking about the call to string being undefined here's a situation where we expect any value of type t that's what this value is it's a value of type t to be determined later we're expecting that this piece of data of type t have a method name string but we haven't given the compiler the information that this constraint needs to exist we've told the compiler that t can be anything but obviously it can't be anything because if that data doesn't know how to string well this function's gonna has no integrity it's gonna panic now let me explore something with you real quick here i can write a function like this right i've defined user i can write a function like this i call this a concrete function right because this function is asking for data based on what it is this function works with user values and only user values doesn't work with our customer values only our user values it's a concrete function but i could also write bill if i could spell today like this this is a polymorphic function this function is using the thumped.stringer interface this function is not asking for data based on what it is it's asking for data based on what it can do what this function is saying is i will accept any concrete piece of data as long as it implements the full method set of behavior defined by stringer the interface is applying a behavioral constraint to what data can be passed into this function at runtime and because we're using a behavioral constraint because we're using polymorphism that means i can pass both user and customer values into this function because they both implement a method name string which satisfies the stringer interface and it's polymorphic because line 15 changes its behavior depending upon the concrete data we're operating on so if i pass in a user value we'll see the stringification of a user if i pass in a customer value line 115 changes because now we see the the stringification of a customer this makes this polymorphic polymorphism means that a piece of code changes its behavior depending upon the concrete data it is operating on this is the polymorphism we have today and go we do it through the use of the interface the interface defines a method set of behavior which really defines a constraint that data must exhibit in order for it to be passed and flow through this function and the compiler is able to a compile time validate that the data we plan to pass across that program boundary does exhibit that full method set of behavior in other words the language already has the concept of behavioral constraint through the use of an interface when we look at our generic functions which are concrete functions however there's special concrete functions right because we're going to be able to at compile time substitute what t is this is considered in in many cases also polymorphic right it's compiler time polymorphism because this code can change its behavior depending upon the data that we pass in based on the type that we use to implement that particular function it's polymorphism but it's a different type of polymorphism but nonetheless t can only be substituted for types where data of that type can exhibit this behavior we have the same kind of problem we have to apply a behavioral constraint so the draft says look we're already using interfaces today to declare behavioral constraints for polymorphic functions this is technically a polymorphic function as well though it's compile time why not reuse the interface to apply the constraints we need on the data that eventually we pass in here so the compiler can validate that this method will exist and that's exactly what the draft is doing so i'm going to replace any with thump dot stringer and now this code compiles and runs because we've now provided the compiler with the proper constraint that is required for that substitution of t pretty cool okay pretty cool so we're reusing the interface um for our traditional polymorphic functions now for our generic functions to apply that behavioral constraint and it works and i like this and it just seems seems right okay there's one more thing i have to show you i promise i'm going to make it quick and it's because generic functions actually bring to it another type of constraint that we don't currently have in the language this is what i could call an operational constraint see our generic functions are substituting t for types but there are certain operations that a function might be applying to data that really doesn't come up in our traditional polymorphic functions but will absolutely come up in our generic functions let me give you an example of what i mean let's remove the generic information generic type declaration for a second you know what i want to do on line 16. now you know it at this point right i want to write a function named add that can take a value of some type t and a second value of the same type t add those two values together and return a new value of type t so we know t is a generic type right so we might come in and do this like i've been doing but that's a problem because i can't say that t can be anything right because look even though t can be an int and t could be a string can t be a user can t be a struct type user no we can't add two user values together in this language we don't have concepts of operator overloading i don't expect that we ever will at least not in a major version one of go and so t can't be anything in fact we can even make an argument that if that's a string that can't even now work either because you can't subtract two strings so you see where i'm going right this is this operation requires a constraint on t because not all data values of a given type can be used in an add subtract divide you get it operation but this is weird this is a problem because this isn't necessarily a behavioral constraint right it's not like i could define an interface with a method set again because we don't have any operator overloading and suddenly this would work this is brand new this is a new problem we need some new syntax but i don't want to define a new type for this type of constraint i want to keep things consistent and so what the draft has come up with to this point is this idea right here now what this idea says is the following we are using interfaces today to define constraints i mean so be it behavioral constraints but nonetheless constraints on what data can be and we'd like to continue to use the interface to define constraints but this isn't a behavioral constraint now it really is a concrete constraint right it's a it's what the concrete data can be in a particular subset of types and so the idea here is that on line 13 we define an interface which represents a list of types that in this case t can be and so we won't say any we'll say add only and now what we're telling the compiler is if anyone tries to call add and the data they're passing in for t isn't a type in this list no good and this supports underlying types by the way so i can pass bills around which is good because then it crosses over package boundaries if you put your user-defined types in here well right you can't extend that across package boundaries so that's interesting there's another interesting kind of caveat to this we can't write traditional polymorphic functions with these interfaces because there's no method set of behavior defined so we suddenly now with this syntax have a set of interfaces that can only be used with our generic functions it can only be used around our our generic functions we can't use them in our traditional polymorphic functions because they just don't define a method set of behavior and these functions here are all about that polymorphism so that is interesting but over the last few months there have been a lot of discussions on the mailing list and other places about this and there just hasn't been a better idea at least one that i have seen now understand something here only one constraint can be applied to a given type and if you have a list of types you can just define the one constraint that would be applied to all or you can individually you know do things like this but at the end of the day only one constraint can be applied you can't have a list of constraints so that creates some restriction in the fact that let's say and i have not found a practical reason for this if somebody ever does i want to see it but this does give you the ability to find an interface that would have both a type constraint and a behavioral constraint now once again i have not found at any level the need for this how that would apply because normally if you have behavioral constraint things like adding or something would be done almost almost here where in that case you might say right you might do something where add takes a t uh and a t and then returns a t or something right and then you would you would use add behavior here and you wouldn't need this anymore because you're going to do it through a polymorphic way where you're going to do it through behavior right and so i haven't seen a situation yet where it makes sense to have both a type list and a method or even this situation where we have a substitution of a generic type to do it either i just haven't seen it yet i'd love to find one because maybe if we don't then it doesn't really matter that we're doing this now one of the ideas though to help is to either create a set of other predefined constraints one of them that we have already today is comparable so if you're going to be writing generic functions they're going to be doing that sort of statement you know comparing you don't have to write your own comparable interface and i i expect that the standard library will probably have a package of these interfaces that are going to be very common for a lot of these types of constraints so we don't have to write them all there's other code in this example that is interesting but because of time i'm going to skip it eric come back to um me please okay so that's my hour and 15 minutes i apologize but we got through a good 80 85 percent of the the draft you saw in that very last example where things maybe are getting a little interesting because we need a new syntax or something we really don't have you kind of see where we're settling out right now and i haven't seen any new discussions around that or anything that's even better now with that being said now is a good time where i want to go through and give everybody an opportunity to either ask a question or i'd like to hear everybody's thoughts i did see some questions while i was talking i can answer those real quick michael asked about the uh any um and if it's going to be an alias to an interface and can it be used outside of generics the answer is it will not be allowed to be used outside of generics that was the initial idea and then everybody started complaining because we didn't want to see any everywhere and so any is going to be restricted to just being a constraint but any does alias or however they're going to end up doing it it's the empty interface uh pretty much at the end of the day um can you have two types vector and a vector t i don't think so adam i haven't tried it in the new syntax but i doubt that you can define a vector and then a vector of some generic type i haven't tried it um i would be shocked if you could though to be honest with you um adam demonstrated um later on that it wouldn't work because as soon as the compiler can interpret type then you'd have vector and it would work out and so collide so yeah it would with the um you're right with the literal construction there'd be ambiguity there wouldn't there you're right so perfect um and then adam you had a thought so adam why don't i start with you i'd love to hear your thoughts you can talk about your thoughts on that zero value and then if you have any other questions too i'd be more than glad to answer them but take like two or three minutes uh no i liked it um it's um it's absolutely difficult to to pitch because uh in terms of how much you cover in terms of generics as a concept you know i'm from like a c and c sharp background so a lot of the stuff was kind of uh it's interesting to see how it goes it's kind of there's always going to be that difference in different experiences so uh that's a problem that you obviously have to solve every single time so it was good though i i liked it and the zero value thing that's just you know bike shedding it's kind of like over three lines of code you know who cares um it's uh you move it closer you might look worse um it's three lines you can't tell it's on a bigger function you might make a difference you know but yeah thanks brilliant brilliant all right the next person in this list is jaisal if i'm saying your name wrong please correct me i'm horrible at names but jaisal do you have anything you'd like to share any questions or any comments about what you saw you don't have to can we add multiple interfaces in front stringer example if you're talking about the er come back to my come back to my code here if you're talking about you can't have a list of interfaces here all right you can only apply one so if you needed that extra behavior to find you'd have to use composition right you'd have to say i'm going to declare the bill interface and the bill interface is going to be everything that the stringer is plus we're going to handle a match or something right doing that right and then you can apply bill here so you can't have multiple interfaces in that list it's one one constraint per type and so it becomes important then to be leveraging composition i hope that answers your question on on that one okay and all right perfect so uh usemen do you have anything you'd like to share with what you saw thoughts opinions questions eric come back to me uh no there's an excellent talk and really clear example i like that all right brilliant uh polio you got anything you'd like to add share thoughts opinions questions okay um all right that's fair vlas demille if i'm saying that wrong i apologize do you have anything you'd like to share add thoughts opinions questions [Music] it's really good i really enjoyed it all right brilliant alan all right and mn nothing from emin that's fair um michael i'm leaving you uh last there so do you have anything you want to add share questions on some of the things that we talked about here in this talk uh i was surprised how many details you have to get right it was fuzzy than expected you think yeah let's just add you guys right everybody pythonize it right so that was a really good time thanks yeah so this is why ian's been working on this for about 10 years and eric come back to my machine for a second there's uh if you want to get a little bit more academic i mean i definitely suggest that you read the draft but there's this link i'm going to give it to eric here uh um eric come off my screen for a second come off my screen i'm gonna drop this link so you can drop it and then eric drop those other three links again into the chat for me but yeah come back to my screen now so if you want to get a little more academic ian and robert you see them in the in the um at the top of this document here they worked on this and a lot of the work that was done on this document came back to making those changes to this new draft release like the use of interfaces and the way they're being used i have not read this yet it's on my list this is kind of like academic stuff so i have a harder time consuming that stuff so i need to really kind of sit back but if you're more into the academic side of this as well then this is a really interesting paper uh that i think you'll enjoy okay eric come back to me there is going to be a recording um once it's available from zoom uh i think michael's going to put it up on a youtube channel something i'm i at arden we recorded this thing i recorded this talk also my name is bill kennedy what are you doing no i also i don't know how that came from uh we'll be publishing this too in the art and labs youtube um as well but it will be there uh for sure all right that's all i've got i really appreciate everybody's time eric drop my email in there too please if you have any other questions or thoughts uh you want to share anything with me eric's dropping my email address there just do that you can find me on twitter on slack whatever you need and that's it back to you michael thanks very much i can see why you're a professional trainer excellent for everybody else uh we don't have a next meet up scheduled yet so if anybody wants to give a talk maybe about generics as well uh please let us uh let either adam or me now we'll put something in for about either next month or month after thank you very much
Info
Channel: Ardan Labs
Views: 6,182
Rating: 4.9153438 out of 5
Keywords: golang, programming, tutorial, Bill kennedy, ardan labs, Development, engineering, online learning, computer science, syntax, generics, go syntax tutorial, golang generics, generics draft proposal, golang syntax, go team, the go blog, golang.org, @goinggodotnet
Id: gIEPspmbMHM
Channel Id: undefined
Length: 89min 48sec (5388 seconds)
Published: Wed Oct 14 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.