I can see in the API docs for Predef that they're subclasses of a generic function type (From) => To, but that's all it says. Um, what? Maybe there's documentation somewhere, but search engines don't handle "names" like "<:<" very well, so I haven't been able to find it.
Follow-up question: when should I use these funky symbols/classes, and why?
: _* is a special instance of type ascription which tells the compiler to treat a single argument of a sequence type as a variable argument sequence, i.e. varargs.
In this case it means "add these to the end." Also note that if a class defines ++ but not ++= then the compiler will treat x ++= y. as x = x ++ y. this is true generally for symbols ending in an equal sign (other than == , != , and = , of course).
Scala some class returns some value if the object is not null, it is the child class of option. Basically, the option is a data structure which means it can return some value or None. The option has two cases with it, None and Some. We can use this with the collection.
These are called generalized type constraints. They allow you, from within a type-parameterized class or trait, to further constrain one of its type parameters. Here's an example:
case class Foo[A](a:A) { // 'A' can be substituted with any type // getStringLength can only be used if this is a Foo[String] def getStringLength(implicit evidence: A =:= String) = a.length }
The implicit argument evidence
is supplied by the compiler, iff A
is String
. You can think of it as a proof that A
is String
--the argument itself isn't important, only knowing that it exists. [edit: well, technically it actually is important because it represents an implicit conversion from A
to String
, which is what allows you to call a.length
and not have the compiler yell at you]
Now I can use it like so:
scala> Foo("blah").getStringLength res6: Int = 4
But if I tried use it with a Foo
containing something other than a String
:
scala> Foo(123).getStringLength <console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
You can read that error as "could not find evidence that Int == String"... that's as it should be! getStringLength
is imposing further restrictions on the type of A
than what Foo
in general requires; namely, you can only invoke getStringLength
on a Foo[String]
. This constraint is enforced at compile-time, which is cool!
<:<
and <%<
work similarly, but with slight variations:
A =:= B
means A must be exactly BA <:< B
means A must be a subtype of B (analogous to the simple type constraint <:
)A <%< B
means A must be viewable as B, possibly via implicit conversion (analogous to the simple type constraint <%
)This snippet by @retronym is a good explanation of how this sort of thing used to be accomplished and how generalized type constraints make it easier now.
ADDENDUM
To answer your follow-up question, admittedly the example I gave is pretty contrived and not obviously useful. But imagine using it to define something like a List.sumInts
method, which adds up a list of integers. You don't want to allow this method to be invoked on any old List
, just a List[Int]
. However the List
type constructor can't be so constrainted; you still want to be able to have lists of strings, foos, bars, and whatnots. So by placing a generalized type constraint on sumInts
, you can ensure that just that method has an additional constraint that it can only be used on a List[Int]
. Essentially you're writing special-case code for certain kinds of lists.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With