<:< operator in scala
Asked Answered
O

8

42

Can anybody provide some details on <:< operator in scala. I think:

if(apple <:< fruit)  //checks if apple is a subclass of fruit.

Are there any other explanations? I see many definitions in the scala source file.

Outandout answered 8/4, 2010 at 19:56 Comment(9)
Are you referring to the method on Manifest or the class defined in Predef?Omega
That's known as the "Madonna wearing a button-down shirt" operator.Part
Ha, I'm calling it the "Angry Donkey" operatorOppressive
Isn't "custom operator creation" one of scalas strengths? Than this could be anything... As you probably know the spec (ScalaReference.pdf) does not mention it.Huron
I probably shouldn't call it an operator - it is not a method on a class, it modifies type parameters, ie: (implicit ev: A <:< (T, U))Oppressive
If only there were a badge for my ridiculous, totally unhelpful comment being upvoted an obscene number of times...Part
Duplicate: https://mcmap.net/q/128639/-lt-lt-operator-in-scala/…Insufflate
@Part There is... but you are not at the obscene level yet. You are still safely M 17+ (game) or R (movie).Unbowed
@Part The "Scala really sucks sometimes" badge would doRisinger
H
26

<:< is not an operator - it is an identifier and is therefore one of:

  • the name of a type (class, trait, type alias etc)
  • the name of a method/val or var

In this case, <:< appears twice in the library, once in Predef as a class and once as a method on Manifest.

For the method on Manifest, it checks whether the type represented by this manifest is a subtype of that represented by the manifest argument.

For the type in Predef, this is relatively new and I am also slightly confused about it because it seems to be part of a triumvirate of identical declarations!

class <%<[-From, +To] extends (From) ⇒ To
class <:<[-From, +To] extends (From) ⇒ To
class =:=[From, To] extends (From) ⇒ To
Hydrodynamic answered 9/4, 2010 at 8:15 Comment(7)
The ones in Predef are generalised type constraints. This mailing list thread goes over some of it: old.nabble.com/…Omega
But from the declarations they all look identical, so how can they mean different things?Hydrodynamic
Thanks for the link: unfortunately I'm still struggling to understand how replacing implicit ev: A => B with implicit ev: A <:< B where <:<[A,B] extends A => B actually does anythingHydrodynamic
There are differences in the variance of the type parameters (eg. -From vs From) and if the implicit method uses a view-bound (e.g. implicit def conformsOrViewsAs[A <% B, B]: A <%< B). I think the difference between ev: A=>B and ev: A <:< B is that the former will take any in-scope implicit conversion whereas the latter will only use the more specific one defined in Predef.Omega
I think you'll find that A => B and <:<[A, B] have the same variance annotations. I've realized what's going on now and it's in the implicit declarations conforms etc as you sayHydrodynamic
@ben dead url replacement, Using generalised type constraints in 2.8 collectionsWail
@Hydrodynamic The difference is not in the declarations themselves but in the objects that inhibit them. For example there is only one object of =:= and the one object is like this =:=[A,A] or A =:= A and there is no way one can construct another object of the type =:=. These facts combined tells me that L =:= R means type L is exactly same as R. In other words =:= acts as a template to prove two types are equal. Hope this is not too convoluted. If it is, please ask and I will clarify to the best of my ability :)Risinger
O
33

The <:< type is defined in Predef.scala along with the related types =:= and <%< as follows:

// used, for example, in the encoding of generalized constraints
// we need a new type constructor `<:<` and evidence `conforms`, as 
// reusing `Function2` and `identity` leads to ambiguities (any2stringadd is inferred)
// to constrain any abstract type T that's in scope in a method's argument list (not just the method's own type parameters)
// simply add an implicit argument of type `T <:< U`, where U is the required upper bound (for lower-bounds, use: `U <: T`)
// in part contributed by Jason Zaugg
sealed abstract class <:<[-From, +To] extends (From => To)
implicit def conforms[A]: A <:< A = new (A <:< A) {def apply(x: A) = x} // not in the <:< companion object because it is also intended to subsume identity (which is no longer implicit)

This uses the Scala feature that a generic type op[T1, T2] can be written T1 op T2. This can be used, as noted by aioobe, to provide an evidence parameter for methods that only apply to some instances of a generic type (the example given is the toMap method that can only be used on a Traversable of Tuple2). As noted in the comment, this generalizes a normal generic type constraint to allow it to refer to any in-scope abstract type/type parameter. Using this (implicit ev : T1 <:< T2) has the advantage over simply using an evidence parameter like (implicit ev: T1 => T2) in that the latter can lead to unintended in-scope implicit values being used for the conversion.

I'm sure I'd seen some discussion on this on one of the Scala mailing lists, but can't find it at the moment.

Omega answered 7/5, 2010 at 15:1 Comment(3)
What's the difference between <:< and =:= ?Deport
@Deport <:< admits subclassing, while =:= doesn't.Unbowed
An implicit parameter ev: T1 <:< T2 asserts that T1 is a subtype of T2. An implicit parameter ev: T1 =:= T2 asserts that they are the same type. See article.gmane.org/gmane.comp.lang.scala.user/18879 for an example of the latter.Omega
H
26

<:< is not an operator - it is an identifier and is therefore one of:

  • the name of a type (class, trait, type alias etc)
  • the name of a method/val or var

In this case, <:< appears twice in the library, once in Predef as a class and once as a method on Manifest.

For the method on Manifest, it checks whether the type represented by this manifest is a subtype of that represented by the manifest argument.

For the type in Predef, this is relatively new and I am also slightly confused about it because it seems to be part of a triumvirate of identical declarations!

class <%<[-From, +To] extends (From) ⇒ To
class <:<[-From, +To] extends (From) ⇒ To
class =:=[From, To] extends (From) ⇒ To
Hydrodynamic answered 9/4, 2010 at 8:15 Comment(7)
The ones in Predef are generalised type constraints. This mailing list thread goes over some of it: old.nabble.com/…Omega
But from the declarations they all look identical, so how can they mean different things?Hydrodynamic
Thanks for the link: unfortunately I'm still struggling to understand how replacing implicit ev: A => B with implicit ev: A <:< B where <:<[A,B] extends A => B actually does anythingHydrodynamic
There are differences in the variance of the type parameters (eg. -From vs From) and if the implicit method uses a view-bound (e.g. implicit def conformsOrViewsAs[A <% B, B]: A <%< B). I think the difference between ev: A=>B and ev: A <:< B is that the former will take any in-scope implicit conversion whereas the latter will only use the more specific one defined in Predef.Omega
I think you'll find that A => B and <:<[A, B] have the same variance annotations. I've realized what's going on now and it's in the implicit declarations conforms etc as you sayHydrodynamic
@ben dead url replacement, Using generalised type constraints in 2.8 collectionsWail
@Hydrodynamic The difference is not in the declarations themselves but in the objects that inhibit them. For example there is only one object of =:= and the one object is like this =:=[A,A] or A =:= A and there is no way one can construct another object of the type =:=. These facts combined tells me that L =:= R means type L is exactly same as R. In other words =:= acts as a template to prove two types are equal. Hope this is not too convoluted. If it is, please ask and I will clarify to the best of my ability :)Risinger
N
14

I asked around, and this is the explanation I got:

<:< is typically used as an evidence parameter. For example in TraversableOnce, toMap is declared as def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U]. This expresses the constraint that the toMap method only works if the traversable contains 2-tuples. flatten is another example. <:< is used to express the constraint that you can only flatten a traversable of traversables.

Newmarket answered 7/5, 2010 at 14:37 Comment(1)
Is this useful outside of the usage as an implicit parameter?Euphonize
U
7

Actually, it checks if the class represented by the Manifest apple is a subclass of the class represented by the manifest fruit.

For instance:

manifest[java.util.List[String]] <:< manifest[java.util.ArrayList[String]] == false
manifest[java.util.ArrayList[String]] <:< manifest[java.util.List[String]] == true
Unbowed answered 8/4, 2010 at 20:39 Comment(0)
D
3

Copy from scala.Predef.scala:

// Type Constraints --------------------------------------------------------------

  // used, for example, in the encoding of generalized constraints
  // we need a new type constructor `<:<` and evidence `conforms`, as 
  // reusing `Function2` and `identity` leads to ambiguities (any2stringadd is inferred)
  // to constrain any abstract type T that's in scope in a method's argument list (not just the method's own type parameters)
  // simply add an implicit argument of type `T <:< U`, where U is the required upper bound (for lower-bounds, use: `U <: T`)
  // in part contributed by Jason Zaugg
  sealed abstract class <:<[-From, +To] extends (From => To)
  implicit def conforms[A]: A <:< A = new (A <:< A) {def apply(x: A) = x}
Deport answered 7/5, 2010 at 14:48 Comment(0)
W
2

To better understand the implementation.

sealed abstract class <:<[-From, +To] extends (From => To)
implicit def conforms[A]: A <:< A = new (A <:< A) {def apply(x: A) = x}

I tried to devise a simpler implementation. The following did not work.

sealed class <:<[-From <: To, +To]
implicit def conforms[A <: B, B]: A <:< B = new (A <:< B)

At least because it won't type check in all valid use cases.

case class L[+A]( elem: A )
{
   def contains[B](x: B)(implicit ev: A <:< B) = elem == x
}

error: type arguments [A,B] do not conform to class <:<'s
       type parameter bounds [-From <: To,+To]
def contains[B](x: B)(implicit ev: A <:< B) = elem == x
                                     ^
Wail answered 4/12, 2011 at 15:28 Comment(0)
A
1

Hmm... I can't seem to find "<:<" anywhere as well, but "<:" denotes subtyping. From http://jim-mcbeath.blogspot.com/2008/09/scala-syntax-primer.html#types :

List[T] forSome { type T <: Component }

In the above example, we are saying T is some type which is a subtype of Component.

Averse answered 7/5, 2010 at 14:34 Comment(0)
K
1

From the sources we have the following explanation:

  /**
   * An instance of `A <:< B` witnesses that `A` is a subtype of `B`.
   * Requiring an implicit argument of the type `A <:< B` encodes
   * the generalized constraint `A <: B`.
   *
   * @note we need a new type constructor `<:<` and evidence `conforms`,
   * as reusing `Function1` and `identity` leads to ambiguities in
   * case of type errors (`any2stringadd` is inferred)
   *
   * To constrain any abstract type T that's in scope in a method's
   * argument list (not just the method's own type parameters) simply
   * add an implicit argument of type `T <:< U`, where `U` is the required
   * upper bound; or for lower-bounds, use: `L <:< T`, where `L` is the
   * required lower bound.
   *
   * In part contributed by Jason Zaugg.
   */
  @implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  // The dollar prefix is to dodge accidental shadowing of this method
  // by a user-defined method of the same name (SI-7788).
  // The collections rely on this method.
  implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

  @deprecated("Use `implicitly[T <:< U]` or `identity` instead.", "2.11.0")
  def conforms[A]: A <:< A = $conforms[A]

  /** An instance of `A =:= B` witnesses that the types `A` and `B` are equal.
   *
   * @see `<:<` for expressing subtyping constraints
   */
  @implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.")
  sealed abstract class =:=[From, To] extends (From => To) with Serializable
  private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
  object =:= {
     implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
  }
Kaon answered 11/3, 2021 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.