You could also be extra-explicit and write: Without the implicit conversion, the compiler complains: You can see how Option.flatten makes use of the ev() function: In summary, all these features fall together to produce something that makes a lot of sense and is useful. Identify and describe Scala's generic type constraints, gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec, How APIs can take the pain out of legacy system headaches (Ep. But these would be less useful and the compiler tries to be more specific when it can. The annotation is known by the compiler. However . But flatten? However the presence of the implicit ev function makes it possible to use the value t of type T as a value of type U. To get the hang of it, lets look at another nice use case , toMap on Traversable: Translated into English: you can convert a Traversable to a Map with toMap, but only if the element type is a tuple (K, V). Other than that, its just an alternate syntax, and really nothing to worry about! Lets look at examples. It seems that the type bound does not work for us. To learn more, see our tips on writing great answers. You probably know types from the standard library such as: The infix notation makes the parametrized type look like an operator, but an operator on types instead of values. Implicits can be used to directly encode type constraints and to construct types recursively. As a reminder, lower and upper bounds are expressed with builtin syntax: >: and <: (spec). to implement methods which can only be used when types are aligned in a certain way, how the simple use cases in the standard library could be implemented differently. All the other methods (except orNull which is similar to flatten) can be called: map, get, etc. This syntax can reduce typing in situations where implicit definitions must be available for lookup but dont need to be directly accessed. This can quickly be proven false: So the two Us are, as seemed to make sense intuitively, bound to each other (they refer to the same type). If we ask ourselves: what does it take for this implicit of type <:<[A, A] to be successfully selected by the compiler? On the flip side, there is just no way Int can be a subtype of Option[B], whatever B might be. and, in some cases, the expressions return type. I confess that I am a bit disappointed that there doesnt seem to be a way to avoid an instanceOf: even though its local to the implementation and therefore the lack of safety remains under control, it would be better if it could be avoided. In Java, you often write things like this, which are rather verbose: URLEncoder.encode() URLDecoder.decode() Since Java 1.5 you can use static imports, but then you are stuck with the orig Generalized type constraints in Scala (without a PhD), Using generalized type constraints - How to remove code with Scala 2.8, Unboxed union types in Scala via the Curry-Howard isomorphism, Restoring an IBM Memory Typewriter - Part 1, Restoring an IBM Memory Typewriter - Part 2, there is an implicit parameter list, with a single parameter called. It seems that those type bounds have not done anything. InGenerics I,we talked about type bounds and use site variance. Join the DZone community and get the full member experience. This is because Lion is not a subtype of Pet. Why is the US residential model untouchable and unquestionable? Instead of this operator being a method, as is the case in general in Scala, it is a type. It wouldnt make great sense to create a Map just out of a sequence of 5 Ints, for example. So our problem comes down to the following question: if somebody requires a function: what constraints must be satisfied so I can pass the following function: With variance rules, we know it will work if: Which means of course that T <: U: T must be a subtype of U! By clicking Post Your Answer, you agree to our terms of service, privacy policy and cookie policy. It is defined as trait Seq[+A]. Since we just want an identity function, which works the same for all types and doesnt hold state, it would be good to use a singleton so as to avoid unnecessary allocations. Scala supports two types of type constraint operators that arent type constraints but are implicit lookupscontext bounds and view bounds. Both of these are converted to implicit parameters: For this reason, you can't combine your own implicits with either view bounds or context bounds, as Scala only permits one block labelled as implicit for any function or constructor. The implicit <:<[A, A] will conform to the parameter <:<[T, U] if it follows the variance rules. You could write it minimally (using <::< in these attempts so as to not clash with the standard <:<): The good news is that the following version, using an intermediate class, works: So this works great, with a caveat: every time you use my version of <::<, a new instance of the anonymous class is created. Other suggestions include Madonna wearing a button-down shirt and Angry Donkey! I think that a good case can be made that it is easier to understand in the case of the relatively simple examples we have seen so far. Here, the obvious matching implicit is findMyLogger, because it returns a Logger. And it not only returns a value of the same type A, but it actually returns the same value which was passed (thats the idea of an identity function). Oops we have lost the Vehicle constraint.
I say more or less because ev could be supplied by any implicit function that converts I to CouponBond. , It is a bit unusual to see an implicit definition which is parametrized with an an abstract type parameter. We also talked about control over abstract types, but there are methods that need to make sure that the abstract type of the class meets certain restrictions only in that method. They are type variables: they stand for actual types that will be decided at each call site (each use of the function in the source code). Immutable Scala collections are covariant, because it is convenient and safe for them to be. You can look at this from a slightly more general angle, which is that a function A => A can only be passed to a function T => U if T is a subtype of U. Announcing the Stacks Editor Beta release! The intent of tupleIfSubtype is to return a tuple of the two values passed, like tuple above, but fail at compile time if the first value is not a subtype of the second value. So what should happen if you call flatten on, say, an Option[String]? . Is there an apt --force-overwrite option? Now the Parking2 method can only receive two identical types that must also be A or a subtype of A! After all, we still miss the deeper understanding of how that magic implicit is defined, and why an implicit search for it may or may not match it. Java arrays are covariant and therefore unsafe! . What does flatten do, as per the documentation? One thing to realize is that we have something unusual here: a method which the compiler wont let us call, not because we pass incorrect parameters to the method (in fact flatten doesnt even take any explicit parameters), but based on the value of a type parameter of the underlying Option class. This takes us to the notion of variance, which is always a fun thing. the constraints expressed in the type parameter section. Is it a bug in the compiler? A weird edge case? The trick is to manage to have an implicit definition in scope which matches only if there is a subtype relationship between T and U. Find centralized, trusted content and collaborate around the technologies you use most. Is possible to extract the runtime version from WASM file? Here is an example that demonstrates upper type bound for a type parameter of class PetContainer: The class PetContainer takes a type parameter P which must be a subtype of Pet. After all, a value must be passed to the function when the implicit is found (when its not found, the compiler blows up so ev doesnt need an actual value). Its just that this is a symbolic name instead of an alphabetic identifier like Map. 6. , It seems that there was another <%< operator as well, but its nowhere to be found in Scala 2.11. Its the result of the $conforms[A] function, which is of course of type <:<[T, U]. If we pass String and Int parameters, we get back a tuple (String, Int) in the Scala REPL: Now lets consider the following modification, which is a naive attempt at enforcing type constraints with type bounds: I know, its starting to be like an alphabet (and symbol) soup! The standard library uses of <:< could be replaced with extension methods, which would achieve the same result via Scala features which are easier to understand and familiar to most Scala programmers. So say that such an extension method is only available on values of type Option[Option[T]]: If we try to apply it to Some(Some(42)), the method is found and the flattening works: If we try to apply it to Some(42), the method is not found and the compiler reports an error: But I see a few differences with the <:< operator: So why not do it this way? To start, lets look at some type bounds that we didnt cover in chapter 6. How do I get a class instance of generic type T?
It could as well have been called SubtypeOf, and maybe it should have been! 4 There is another similar construct, =:=, which tells the compiler: 5. However this early implementation had issues related to implicit search, therefore a new solution was implemented in 2.8 and <:< was introduced.
This is much safer than using t.asInstanceOf[U]. But this funny <:< sad-with-a-hat 1 operator 2 was entirely new to me! It turns out that we were not adding bounds to our A, but defining a new type A . And we have seen above that <:< extends T => U. what is a constraint then? On the plus side, when used <:< as a recipe, it is easy to understand and useful, and I cant help but being impressed that generalized type constraints are implemented at the library level, and that they can emerge from powerful underlying language features such as type inference and implicits. Lets look at the implementation of <:<, which we find in the Scala Predef object 11: Lets think about a simpler case of implicit search: The compiler, when you write makeMeASandwich without an explicit parameter, looks for an implicit in scope of type Logger. So even though the explanation of how <:< works might seem a bit tricky, you can take comfort in thinking that in other languages this might be compiler code, not library code. What is it? Do you think that the compiler will accept to compile this? Type bounds to the rescue! This makes you think that there are more specific type constraints. , Thats how all implicit searches work, see Where does Scala look for implicits?. So using an implicit value class as I did above was not a great option at the time. How should we do boxplots with small samples? To summarize the reasoning: the only eligible implicit definition in scope which can possibly be selected by the compiler to pass to our function is selected if and only if T is a subtype of U! Lets try to add bounds to A for the methods parkMoto and parkCar: If you put this in an IDE, this will give you clues Suspicious shadowing But it does not matter, it compiles and we will try it! Cannot Get Optimal Solution with 16 nodes of VRP with Time Windows. Bad language design? What are the possible constraints, what do they do, and what's an example of when to use them? If so, Ill use it, otherwise Ill continue my search (and fail if the search ends without a match).. Is there an easier ways to achieve the same result? The type in the middle (the infix type proper) can be referred to a an infix operator. Over 2 million developers have joined DZone. the types of the parameters we actually pass to the function. rev2022.7.21.42638. And what will happen if I create a Parking [Car] and call it parkMoto? So here we are: the implementation is explained! You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers. And if we type the return of the method to be sure? Another way to say this is that <:<[A, A] must conform to <:<[T, U]. This is the third article in our series on Scala Generics (we have already looked atupper and lower Scala type boundsandcovarianceand contravariance) andtoday, we are going to talk about constraints to be more specific, we're going to talk about generalized type constraints. S <% T is a view bound, and expresses that S must come equipped with a view that maps its values into values of type T. It's confusing for me too, and I have a Masters in programming languages from Berkeley. But I can think of a few questions: Lets look into each of these questions in order. Jason Zaugg appears to be the mastermind behind it. From a syntax perspective, it is a regular annotation, which applies to the abstract class <:<. These are the same T and U we have in the first parameter list. Instead, the effective T and U are the result of the compiler working its type inference algorithm, given: then yes: T = String and U = Int because there are no other constraints. , Using generalized type constraints - How to remove code with Scala 2.8. But lets stay calm: the only change is that instead of specifying just T as type parameter, we specify T <: U, which means T must be a subtype of U. And there is only one implicit definition with type <:<-of-something in the whole standard library, which is: Now there is a bit of a twist, because the implicit is of type <:<[A, A], with a single type parameter A, which in addition is abstract. We have not even entered into what their implementations are! This is the same as Function1[-From, +To] and in fact <:< extends Function1! 15 You can in fact test the matching logic very simply with the built-in identity function: The same works with $conforms, which returns an <:<, which is also an identity function: So it is a neat trick that the library authors 16 pulled off here, combining implicits and conformance of function types to implement constraint checking. The three existing generalized type constraints are =:=, <:<, and <%<. How can I use parentheses when there are math parentheses inside? Normally you expect ev be the identity function, but of course somebody could have written an implicit conversion from say DiscountBond to CouponBond which would screw things up royally. Its a type: the same kind of stuff as Map or Either, in other words, typically a class or a trait. But I also understand how some programmers 19 might be bothered by all the machinery behind features like this. So this article is about figuring out what it means and how it works. , For more uses of <:<, see Unboxed union types in Scala via the Curry-Howard isomorphism by Miles Sabin. Thats why its commonly called ev, for evidence: its presence stands there as a proof that something (an implicit) exists. But that wouldnt be very satisfying, would it? Its a bit trickier than it should be in order to prevent extra allocations. Without the notion of covariance and contravariance (where subtyping of the type argument goes in the opposite direction as the enclosing type), you: Besides collections, functions are another example where variance and contravariance matter. In Scala, type parameters and abstract type members may be constrained by a type bound. If you do need to use your own implicits then you must first manually convert any such bounds to the unsugared version and add this to the implicit block. Dog and Cat are subtypes of Pet so we can create a new PetContainer[Dog] and PetContainer[Cat]. As for me, I am keeping generalized type constraints in my toolbox, but I like seeing the feature as a gateway to a more in-depth understanding of Scala.
So lets keep going! In other words, the restrictions should not go on a new type B, but on the type A defined in the class. If you have done some Scala for a while, you know about pattern matching and match/case. Type level programming and compile time execution. Scala Generics: Generalized Type Constraints (Part 3), Building a REST Service That Collects HTML Form Data Using Netbeans, Jersey, Apache Tomcat, and Java, Tutorial: Build a Full-stack Reactive Chat App With Spring Boot, The Benefits of Open Source and the Risks of Open Core [Recording]. The authors of the standard library could have used =:= to say that the type has to be exactly an Option[B], but using the subtyping relationship allows the result of the expression to be a supertype. So this makes sense. This might be, after all, how the term generalized type constraint gets its name. I see two possibilities: The authors of the Scala standard library picked option 2, and I think that its a good choice, because most likely calling flatten in this case is not what the programmer intends. 14. Infix just means that a type appears in the middle of two other types, the same way the + operator appears in the middle in 2 + 2. How does this conformance work? So for now, lets take this as a recipe, a trick if you will, while we look at how this can be useful in practice. The why and how of this feature is the subject of the rest of this article! That means that in A <%< B, A must be able to be seen as B. Here both T and U are abstract: we dont know what they will be when we write the function. , The compiler could have chosen the solution Any / Any, or AnyRef / Any. Functions can have more than one parameter list. Instead, Scala has, via implicit conversions, what in effect achieves extension methods (AKA the extend my library pattern).
How do I address unchecked cast warnings? On the other hand, <:< is a more flexible library feature which you can reuse easily and even combine with other implicits, like in this example using Shapeless: 18. The idea is that a method such as flatten does not need to be included on the base class or trait, in this case Option. mv fails with "No space left on device" when the destination has 31 GB of space remaining. An upper type bound T <: A declares that type variable T refers to a subtype of type A. One thing you might wonder is what to do with the ev parameter. Consider a Scala immutable Seq. Making statements based on opinion; back them up with references or personal experience. And today, we are going to work with this small set of classes: In this example, the parking method can return any type of vehicle, just as the upper type bound of Parking specifies, but what happens if we want to have specific logic to park cars and motorcycles, kind of like this? So lets write the solution, without necessarily understanding it fully yet: Because type inference goes parameter list by parameter list, lets start with the first one. , The compiler needs to be able to figure out conformance of types outside of implicit search, including every time you pass a parameter to a function. Where developers & technologists share private knowledge with coworkers, Reach developers & technologists worldwide. The following deconstruction turns out to be fairly long, but even though <:< itself may not be useful to every Scala programmer, it touches a surprisingly large number of Scala features which most Scala programmers should know. These constraints can be used in different ways, but the most interesting thing is that they allow us to delimit the type parameter of the class in the same method: By using =:=, an abstract type such as Parking forces its type parameter to be a specific one for different methods. It is not enough, since we are adding restrictions on the type of return, but we want to work with v: A. Its there first so the compiler can check the constraint. But in fact <:< acts exactly like an implicit identity function under another name! So its relatively easy to imagine how the compiler goes through the implicit search path, checking each available implicit, and pondering: Does this particular implicit have a type which conforms to the required implicit parameter type? Please let me know! And thats exactly what we were looking for! To contrast with regular type bounds, if you write: the compiler knows that T is a subtype of U, thanks of the native semantic of <:. The answer is that one should be able, for some type A to be determined, to pass a value of type <:<[A, A] to the parameter of type <:<[T, U]. Missing value classes meant boxing overhead when running extension methods, while missing implicit classes just meant more boilerplate. Lets try using use site variance, if we remember, use site variance allowed us to define the constraints of a generic type at the moment of defining it: It seems that this can be a solution, although we have had to sacrifice several things we use the methods parkMoto and parkCar outside Parking, passing a parking as a parameter In addition, the open-close principle has been broken by calling parking.v (tell, do not ask). These implicit parameters, generally called ev (evidences) are tests, which show that a type meets certain restrictions. I think that this argues against the presence of <:< in the standard library, especially at the level of Predef, and if this was introduced today, my inclination would be to recommend leaving it to third-party libraries such as Shapeless which actually benefit the most from this kind of advanced features. Blondie's Heart of Glass shimmering cascade effect. It is analogous to the type bound <:. This is very useful, as we have seen. These three Scala Generic articles are just an introduction to generics. For example we dont say that U is a Banana, which would be a concrete type. We have also seen how we can use instead a generalized type constraint expressed with <:<: So is <:< is worth it? This is part of a feature called generalized type constraints. There is at least one other way something like what <:< does can be achieved. 465), Design patterns for asynchronous API communication. We could try using an object, since thats how we do singletons in Scala, but thats a dead-end because objects cannot take type parameters: So in the end the standard implementation cheats by creating an untyped singleton using Any, and casting to [A <:< A] in the implementation of $conforms. So I can pass a function with types different from Banana and Fruit without breaking expectations as long as the function: This is the magic of variance, and you can convince yourself that it makes sense from the point of view of the process function: expectations wont be violated.