Nice video. What about adding an extra layer of compiling validation with protocol. Something like: protocol BloodType {} enum OPositive : Blood Type {} struct BloodSample {...} So, now, not only cannot the compiler compare bloodsamples with different phantom type, now cannot compiles Bloodsamples with a Type that make no sense to use as a tag.
Nice explanation. T in the undefined func is the Base value because it represents the actually that is required to fill the hole. You can also see it as a generic placeholder. if print T you will see that it is either a String or Int depending on the Base type that wasn't defined.
I think is cause in that case, as with Structures, you are able to make enum of that type, and the point of the phantom types is to not be able to make instances of those types; just use them to make the compiler fails.
21:27 So there is no compiler error for not defining generic type as it will infer from the function return type as it is the only statement which will infer it to return... Woah...so much inference 😅
Great explanation! I appreciate this understanding so I can tell what is going on if I see it used in some code, however, I'm not sure that this is something I would do. Firstly, it seems like a compile error to declare a Generic without using the type. Secondly, at least in the examples given, there does not seem to be a reason for this. In the first example of generating a compile error by adding 2 ints of different usages, it is avoiding a bug that would be pretty easy to catch with proper naming conventions and would be mostly caused by an autocomplete typo and would actually eliminate the ability to properly add two ints where you might want to (like to create a hash or something). Seems just unnecessary. In the second case of not adding together blood volumes of different blood types, this could be done without any "phantom" types, just use non-phantom Generics and real enums, you could even do it with the types as shown, but probably would actually do it by exposing a method to add volumes of blood, not simply use the raw property and the + operator. It seems to add a level of "confusion" to the code rather than providing a real benefit that couldn't be achieved in some more implicit way.
In the blood types example, if each one is a different type, you can't really have a variable or property that gives the user's blood type. This would have to be a set of separate declarations for processes where the risk of mixing types was so severe that you wanted to make it impossible to code for.
Regarding the state machine: One *could* utilize them in notifications. If you have an entirely event driven architecture and your state machines represent fully transient information, there's no actual need to store them. However, this is a great limitation of usefulness or one would have to come up with techniques that I'm simply not aware of. A compromise position could be if we enabled the Swift compiler to figure out that your generic type should be covariant: forums.swift.org/t/possibility-of-an-explicit-covariance-attribute-for-generic-types/13999/4 Then, a machine in any state can simply be represented as Machine (I'm not aware that this would be possible today). One would have to downcast the machine then in order to apply the arrow (which introduces runtime checks and compromises the type safety), but at least composition of transitions can still be done in a safe way. This can still be useful if there is actually some logic in the transition function, e.g. each transition attaches some payload to the machine (meaning that one would have to write out the actual transitions for each pair of supported types).
This is really quality content and it's rare that you find this level of quality is swift tutorials on TH-cam.
Love the state machine example
I wish I could upvote this more than once, even though I don't fully understand it.
This actually blew my mind 🤯. Been using Swift since 1.1 and had no idea that this kind of compile safety is possible.
Really cool concept! I believe this is what RxSwift uses to represent Traits
Mind blowing! Thank you, Paul!
Such a great explanation! Thank you!
Nice video.
What about adding an extra layer of compiling validation with protocol. Something like:
protocol BloodType {}
enum OPositive : Blood Type {}
struct BloodSample {...}
So, now, not only cannot the compiler compare bloodsamples with different phantom type, now cannot compiles Bloodsamples with a Type that make no sense to use as a tag.
Great idea, pity Paul never replies on videos.
Nice explanation. T in the undefined func is the Base value because it represents the actually that is required to fill the hole. You can also see it as a generic placeholder. if print T you will see that it is either a String or Int depending on the Base type that wasn't defined.
Very interesting. From now on, I'll use it everywhere.
Good point the state machine!!
Reminds me of Units of Measure feature in F#.
Paul, amazing video.
Why do you use empty enums instead of, for example
enum BloodType {
case: 0Positive
case: APositive
//Etc.
}
?
I think is cause in that case, as with Structures, you are able to make enum of that type, and the point of the phantom types is to not be able to make instances of those types; just use them to make the compiler fails.
21:27
So there is no compiler error for not defining generic type as it will infer from the function return type as it is the only statement which will infer it to return...
Woah...so much inference 😅
Nice explanation,
Can we have more examples for PHANTOM types ?
Great explanation! I appreciate this understanding so I can tell what is going on if I see it used in some code, however, I'm not sure that this is something I would do. Firstly, it seems like a compile error to declare a Generic without using the type. Secondly, at least in the examples given, there does not seem to be a reason for this. In the first example of generating a compile error by adding 2 ints of different usages, it is avoiding a bug that would be pretty easy to catch with proper naming conventions and would be mostly caused by an autocomplete typo and would actually eliminate the ability to properly add two ints where you might want to (like to create a hash or something). Seems just unnecessary. In the second case of not adding together blood volumes of different blood types, this could be done without any "phantom" types, just use non-phantom Generics and real enums, you could even do it with the types as shown, but probably would actually do it by exposing a method to add volumes of blood, not simply use the raw property and the + operator. It seems to add a level of "confusion" to the code rather than providing a real benefit that couldn't be achieved in some more implicit way.
amazing as usual
As usual - great lesson! =)
Awesome!
In the blood types example, if each one is a different type, you can't really have a variable or property that gives the user's blood type. This would have to be a set of separate declarations for processes where the risk of mixing types was so severe that you wanted to make it impossible to code for.
You can, but you have to wrap it in an enum, and it gets a bit messy, and sort of turns a compile time check into a run-time check.
Regarding the state machine:
One *could* utilize them in notifications. If you have an entirely event driven architecture and your state machines represent fully transient information, there's no actual need to store them. However, this is a great limitation of usefulness or one would have to come up with techniques that I'm simply not aware of.
A compromise position could be if we enabled the Swift compiler to figure out that your generic type should be covariant:
forums.swift.org/t/possibility-of-an-explicit-covariance-attribute-for-generic-types/13999/4
Then, a machine in any state can simply be represented as Machine (I'm not aware that this would be possible today). One would have to downcast the machine then in order to apply the arrow (which introduces runtime checks and compromises the type safety), but at least composition of transitions can still be done in a safe way. This can still be useful if there is actually some logic in the transition function, e.g. each transition attaches some payload to the machine (meaning that one would have to write out the actual transitions for each pair of supported types).
Great video!
Amazing Paul
So that generic should only be a enum?
But database query returns role as property so mapping would require more effort
Wait, isn’t this supposed to be a plus article?
It is; as it says near the beginning and at the end this is a free sample.
But... why?