How to use phantom types in Swift

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] phantom types are a powerful way to let us give the Swift compiler more information about our code in this video I'm going to walk you through why you'd want them and how they work and most importantly give you lots of sample code you can try for yourself now this long video is actually a free sample from my hacking with Swift plus program which can find out more about up there hacking what if comm slash plus there I post subscriber only videos going in real detail about topics like this one all in ultra 4k HD video over there at least plus articles code and more is lost check out at that URL god have a look here I've got a simple Mac OS terminal project a great place that has noodle around without Swift ey or UI kit or similar just plain old Swift code and the basic definition of a phantom type is this some generic type that does not use at least one of its generic parameters that's it now we see the code you like that's easy it's obvious but you're not quite sure why we'll come on to that first things first it's this example code of what a phantom type looks like I could say there's a struct employee that is generic over some kind of role and conforms to the equatable protocol and inside there I'll say that is one property called name which is a string and that's it so it's generic over some kind of role and performs equatable but has not used that generic type parameter the role inside the type itself or the type to definite definition which is a string right and that is his phantom type that's it right there it's that simple and and you're thinking why the heck would you want that I get understand entirely what come on just a second this is different from regular generic types like in foundation there's a type that looks like this struct measurement that's a generic over a unit type and it has a unit unit type and a a value double sound like that that's the measurement struct more or less from foundation which has some more code behind it that's the basics of it that is a regular generic that's not a phantom type you can see it's generic over a unit type and that is used in the definition right there so it is not a phantom type so the definition is real simple if right it would you don't know is something roll but we aren't using it it's a string here so the real question is why the heck would you want that what does that give us so we could very easily say well actually if it's struct employee and we aren't using roll boom make it not generic same result not quite as an important difference although we don't really use the parameter here not used at all Swift knows about it Swift will enforce the rules for us have an extra bit of complexity there so it knows about it and that's important for example if you worked for a software company and there you had programmers and you had sales people and they're both employees they're different kinds of employees you wouldn't want programmers doing sales hopefully and you would want sales doing programming at least on this company we can represent this difference with a no case enum we could say there's an enum sales and an enum programmer boom between sales and programmer now it's a good idea to here to use an okay's enum rather than struct sales and struct programmer because you can make a sales when it's a struct you could say you know let su equals sales and make an instance of that type whereas if it's an enum that isn't possible anymore so you're making it clear to the other developers on yourself for the future quite frankly that this is not designed to be made so it's an enum with no cases like this now let's say you had a friend called Zoe who worked in the coding team for your company you could represent her in code by saying this lets o'yone be equal to employee programmer with the name of Zoe and if you had another friend also called Zoe who worked in the sales team you could represent her by saying this let Zoey to equals an employee in the sales team also with the name of Zoe now we've made employee conformed to equatable which means the Swift compiler will synthesize for us and equals equals function to compare employees and we'll compare them line by line for their parameters a property sorry so here it's named so I'll do his name equal to name so we should go to say this print Zoe 1 is equal to Zoe - and that's great except boom we can't we cannot compare those two things anymore because we are no longer comparing an employee against an employee we're comparing an employee programmer against employee sales and so it's telling us these aren't the same type anymore so even though we aren't using the type Swift understands Swift stops from compiling so if thinks these two are different you shouldn't compare these two things if we had in comparison made the role a property if it said you know var role was a string and then made it up here just regular employee we might say you are an employee with the name Zoe and the role of programmer and then employee sales with the name role of Sales the difference is mucked the codelab compiles and at runtime yes they're different I returned false one is a programmer one its sales it'll compare the role and say they're not the same but the code now compiles and that's the power of phantom types we are giving Swift extra information that clarifies how things should work and allow it to work harder on our behalf and when done well it's gonna allow logic errors to surface as compiler errors you've made a fundamental mistake Swift will literally not allow you to build your code could you're trying to do something doesn't make sense which is great now let's look at some other examples to give you more flavor for this if you're working in a hospital that handled blood samples you're analyzing blood temples perhaps you might start by finding some of those possible blood types I don't do all of them just some you might say there's an enum of a positive like this an enum of a positive and then enum of B+ and there are more and now you can define a blood sample struck next generica was some kind of blood type like this struct blood sample check oh it's hype let ammount be a double and then because we can we'll add some operator overloading we'll say static func + LHS is a blood sample RHS is a blood sample returns a new blood sample and we'll just say there's new blood sample with the amount being NH s dot amount plus rh s dot amount add the two blood samples together and it's got this blood is plus operator here so you can combine blood sample you can say well we've got 5 mils here of o+ and 10 mils here o positive it's just a positive put them together but Swift now understands that two samples and different blood types are different and stop us from making mistakes so with example we could say let sample one be a blood sample of a positive positive with the amount of five mils then let sample to be a blood sample of a positive with the amount of oops with the amount of five mils again and at sample three be a blood sample with her o positive again and we'll do a mount of amount of seven mils boom and so now this code will work let combined 1 equals sample 1 plus sample 3 it knows they're both a positive that's ok can do that but we cannot say let combine 2 equals sample 1 plus sample 2 because there are different blood types and they're different things so you cannot mix blood types like that it doesn't work like that that's gonna actually hurt somebody if not kill somebody giving that kind of inflation to somebody so suppose a noun understands this is not a good thing do not allow us to mix these types sometimes the difference is a less subtle for example if we had a struct user and we had let ID int and let age int so two integers like this and they're both plain integers so there's nothing stopping us from saying let user equals a user ID 53 age 53 and then do print print even user ID is equal to u 0 H that's valid code they're both integers if of course you can do that and yes there entacle that's gonna print true but if you are comparing user IDs to user ages or user shoe sizes you've almost certainly made a logic error that makes no sense and it's a mistake that phantom types can catch for you that's the point of them so again first you make no case in ohms we're going to say as an enum of user ID come on user ID there we go and we'll do a num of age like that and next we make a struct Nero is some kind of type like this we don't care what but something so we'll say straw tag is Directorate type and is equatable VAR value hint so it's gonna hold a single integer but it's tagging it somehow and now we can modify our user strike we can say that ID is not int anymore it's a tag of user ID that an age as a tag of age and we can now initialize that with integers directly to it for ease of use ID int age int self dot ID is equal to tag of value ID itself to H equal to tag value is the age like that and now trying to compare ID against age makes no sense Swift understands it is not a good idea don't do that now in practice I'd probably read you this a slightly I pro say let's make tag conform to the expressible by insta literal and then add the initializer for that which is in its way if instead of integer literal value into a literal type self dot value equals value value so I'll have that conformance instead because now that allows Swift to make these tags for us directly from an integer which is nice so we can get rid of this whole code up here and let's Swift synthesized that for us which is much much nicer way of working it's the one compiled of course that's still tagged but Lisa concept is now simpler which is great now for a broader solution to the same problem I've got an article about this called improving your swift code using value objects I'll link to it down below have a look there for that but the most important thing to remember here about phantom types is that they're they're empty that generic parameter isn't used anymore of more than one if you choose to and the Swift compiler is fully aware of them anyway it takes them into account it's comparisons and it's synthesized methods and so forth it understands what they are that means you can use them as constraints and extensions and more they're really powerful now there are lots of practical ways you can use phantom types to build interesting stuff and I came across one fascinating experiment from Suroosh conlou in this example soosh uses phantom types to build a state machine where invalid transitions cannot compile swift will not allow it now state machines are pieces of code that designed to move between a series of predefined States for example a vending machine might say I am waiting for a customer first state then a coins been inserted second state then fetching selection the third state and then serving selection the fourth state and it moves from A to B to C to D then back to a again and this is nice because you know you can't go from waiting for customer to serving selection because you haven't had a coin inserted in there you haven't had fetching selection happen as well so you can't we don't want to go from A to D now you could do that of course with fatal error or whatever you want in your code but with phantom types we can actually encode that into the compiler so that invalid transitions don't compile now Suroosh very kindly gave me permission to use this example code Here I am gonna simplify a little bit because I want to focus on the part that really matters so let's have a look at that now let's go into here first we're going to define the state's our machine can be in and this example will have the four that define a vending machine so I'll say there's an enum waiting we are waiting for customer to come along enum coin inserted there money's in the slot what they want to order then enum fetching we are now looking around for the item and then enum serving we are serving the item to the customer second we're going to make a phantom type that stores a transition between any two states from state to state that's what it does so we'll say a struct transitioned from two and be empty it you just neither of those phantom at those am Jeric type parameters then at neither user especially phantom time and that's fine the structure literally empty bat okay cuz swift able to look at those title under and what they mean third will make a machine struck that represents our state machine in any one given state and this is gonna have a method a single method to transition between the current state to a new state so we'll say struct machine generic of a state then func transition to a new state with transition and this is going to be a transition from that state to a new state and return a new machine to the two state like that inside here I'll just do in it send back the correct machine now that's hard any code that's not a lot of kurtal but it's quite dense so let's look at it in detail this machine strut is generic over some kind of state and this transition method is you know has some kind of two state but it also uses this state from the machine struct like that and so we're saying it has to go from wherever we are currently to some other kind of state and return a new state machine that it knew its current site is the to state so then the new machine has its current state set than the new state and then using doughnuts here means to send back the new machine just fine now we can go ahead and define all the valid transitions for our vending machine so we're gonna say let's start equals a transition from waiting to coin inserted so the customers put their money in the slot coin insert it lets selection mode selection mode actually made him be a transition from coin inserted to fetching then let the livery be a transition from fetching the item to serving the item and let reset when it's all gone through to be a transition from serving back to waiting boom and that's it that's our entire state machine defined that's all code it takes and it's quite brilliant we can now create a machine with a particular state we could say let m1 be a machine of type waiting and we can step through two other possible by the states we could say let M to be m1 dot transition with start move to the start state we're going to coin to be inserted and then we'll do let m3 equals m2 dot transition with selection made then m3 or m4 sorry BM 3 dot transition with delivery let m5 b m4 dot transition with reset and they're all valid states and the magic here is that swift will check all those transitions at compile time to make sure they're valid it will it's just not possible to make an illegal state so if I had let m6 b m5 which is a machine in the waiting state as you can see dot transition with delivery try and deliver before in a coins been made or sections may boom it's saying you can't do that it is not possible to do that with your machine which is brilliant I think this is a fantastic experiment because it shows us how we can leverage the Swift compiler to prove that our code is correct it's proving our code is correct it's saying you cannot do that's wrong code it's not allowed I think it also makes a fascinating exploration of phantom types in action however in practice this solution doesn't work so well and to explain why I asked Suroosh himself here's what he said I originally wrote this as an experiment to see if I could have the compiler protect me from performing invalid transitions on a state machine but I quickly realized that changes to this state would cause the type of the entire machine to change meaning that it couldn't be stored back into the same variable this prevents um having it as a property on say a view controller with transitions modifying the state machine so fun to try out a little impractical in actual usage there before I finish there's one more technique I want to demonstrate and it quite different from all the earlier examples let's clean up our code it's this piece of code func undefined t take some sort of message string equal to nothing by default now it turns at E and we'll do fatal error and then we'll do undefined message boom that now this is not a phantom type but it is a great example of using Swift's type system in interesting ways and I first saw this being used in a talk by johannes vise from be swift neo team where he presented this idea as a useful global function to have around your code while you're still building your app and this function undefined T can act as it pretty much any other kind of value you like because it's Jericho or tea it'll when doing and it returned the tea but inside of course doesn't really do that it's actually use inside the function but fatal error at returns never a special type which means this code will never return the sort understands it hasn't got to really return a tea which is great so that allows a code to compile and be used and this set up is brilliant because it allows it to work as a placeholder in your code just to make your code compile while you're busy doing something else you're doing a to other things at once and you'll come back this in a minute but it's for now you want a code to compile cleanly undefined it's great for that use filling vacant parts and it'll just work as a placeholder for the real thing and you can use it however you want you could say let name be a string I'll use undefined example string that's fine or let score int being undefined with example int and you can even make act as placeholders inside functions if you haven't written yet if you want to we could say func user ID for username string returns an optional int I haven't been this code yet so I'm going to do to find for that username and it'll work it'll figure out to return an optional int it's great now in this instance you might think well if that's a placeholder for some code that I haven't written yet I could just say fatal error this isn't working yet but in practice yes you could of course it could but fatal error also has other meanings you can have fatal error legitimately elsewhere in your code and so when it comes to finding and removing the undefined instances later on it's very easy to look for undefined this means it has to be replaced later on whereas you might have other fatal errors doing real things in your code you can even use repeatedly could say so and like let timer be a new timer and you know give it loads of values here you can see it is like um time interval I don't know undefined target undefined select the undefined you didn't for undefined repeats undefined I don't know I'll come back and fill us in later with real values and yes you could put in magic numbers you could put in oh I don't know it's just guessed true for now or I don't know let's just guessed hammington all zero for now you can totally put in magic values instead just like you'd use fatal error but the magic of undefined is it'll crash assumes is touched it will call fatal error as soon as it's wrapped code actually used where as a time interval of zero repeats true you might forget was just placeholder code you might forget to come back to and fix it later on and that magic therefore is that undefined only worked at compile time it's compile time hole filler a compile time placeholder that'll definitely definitely crash your code at runtime which is really nice it just helps you push your code forward it would get your project moving before you go and fill in all the gaps which is really nice now of course you may have the question if undefined T is not a phantom type which is not what is it and to be honest I had exactly the same question so I went back to the source and asked yo.hannes here's what he said I wouldn't call undefined T a phantom type because first of all really it's a value well a value that will never generate but still even calling T a phantom type isn't quite right T can be anything so it could be a string which is far from a phantom type I think the closest somewhat correct term I have for this is it's called the bottom value usually written as an up tack and even that is probably not right because the bottom type is defined as not having values so the term bottom value isn't great either so I guess we could say undefined return type is bottom I love that answer from your harness because it shows just how new once the discussion is anyway that finishes up our discussion about phantom types you've seen how they work why you'd want them I given you practical examples go to law to try them out we even tried that experiment from Suroosh to about a state machine entirely with these things I even showed you how the undefined T function acts as a general placeholder for our code while we're still working it's not a phantom type but it's still fascinating thanks for watching this video folks again this was a free sample from hacking wolf plus a subscriber only area on my site where I post lots more videos like this one all in 4k Ultra HD with massive articles it's hash code samples and more for information check out this URL hacking will calm slash plus
Info
Channel: Paul Hudson
Views: 12,206
Rating: undefined out of 5
Keywords: swift, phantom, type, generic, foundation, xcode, macos, tutorial
Id: 9Vs4YDSpzHA
Channel Id: undefined
Length: 25min 0sec (1500 seconds)
Published: Wed Jun 17 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.