Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is `class A[_]` useful for?

The types of symbols class A[_] or of def a[_](x: Any) have a type parameter that can't be referenced in the body, thus I don't see where it is useful for and why it compiles. If one tries to reference this type parameter, an error is thrown:

scala> class A[_] { type X = _ }
<console>:1: error: unbound wildcard type
       class A[_] { type X = _ }
                             ^

scala> def a[_](x: Any) { type X = _ }
<console>:1: error: unbound wildcard type
       def a[_](x: Any) { type X = _ }
                                   ^

Can someone tell me if such a type has a use case in Scala? To be exact, I do not mean existential types or higher kinded types in type parameters, only those litte [_] which form the complete type parameter list.

like image 367
kiritsuku Avatar asked Oct 08 '12 09:10

kiritsuku


4 Answers

Because I did not get the answers I expected, I brought this to scala-language.

I paste here the answer from Lars Hupel (so, all credits apply to him), which mostly explains what I wanted to know:

I'm going to give it a stab here. I think the use of the feature gets clear when talking about type members.

Assume that you have to implement the following trait:

trait Function {
  type Out[In]
  def apply[In](x: In): Out[In]
}

This would be a (generic) function where the return type depends on the input type. One example for an instance:

val someify = new Function {
  type Out[In] = Option[In]   def
  apply[In](x: In) = Some(x)
}

someify(3) res0: Some[Int] = Some(3)

So far, so good. Now, how would you define a constant function?

val const0 = new Function {
  type Out[In] = Int
  def apply[In](x: In) = 0
}

const0(3) res1: const0.Out[Int] = 0

(The type const0.Out[Int] is equivalent to Int, but it isn't printed that way.)

Note how the type parameter In isn't actually used. So, here's how you could write it with _:

val const0 = new Function {
  type Out[_] = Int
  def apply[In](x: In) = 0
}

Think of _ in that case as a name for the type parameter which cannot actually be referred to. It's a for a function on the type level which doesn't care about the parameter, just like on value level:

(_: Int) => 3 res4: Int => Int = <function1>

Except …

type Foo[_, _] = Int
<console>:7: error: _ is already defined as type _
       type Foo[_, _] = Int

Compare that with:

(_: Int, _: String) => 3 res6: (Int, String) => Int = <function2>

So, in conclusion:

type F[_] = ConstType // when you have to implement a type member def
foo[_](...) // when you have to implement a generic method but don't
            // actually refer to the type parameter (occurs very rarely)

The main thing you mentioned, class A[_], is completely symmetric to that, except that there's no real use case.

Consider this:

trait FlyingDog[F[_]] { def swoosh[A, B](f: A => B, a: F[A]): F[B] }

Now assume you want to make an instance of FlyingDog for your plain old class A.

new FlyingDog[A] { ... }
// error: A takes no type parameters, expected: one
// (aka 'kind mismatch')

There are two solutions:

  1. Declare class A[_] instead. (Don't do that.)

  2. Use a type lambda:

    new FlyingDog[({ type λ[α] = A })#λ]
    

or even

new FlyingDog[({ type λ[_] = A })#λ]
like image 110
kiritsuku Avatar answered Oct 17 '22 03:10

kiritsuku


I had some casual ideas about what it could mean here:

https://issues.scala-lang.org/browse/SI-5606

Besides the trivial use case, asking the compiler to make up a name because I really don't care (though maybe I'll name it later when I implement the class), this one still strikes me as useful:

Another use case is where a type param is deprecated because improvements in type inference make it superfluous.

trait T[@deprecated("I'm free","2.11") _, B <: S[_]] 

Then, hypothetically, one could warn on usage of T[X, Y] but not T[_, Y].

Though it's not obvious whether the annotation would come before (value parameter-style) or after (annotation on type style).

[Edit: "why it compiles": case class Foo[_](i: Int) still crashes nicely on 2.9.2]

like image 26
som-snytt Avatar answered Oct 17 '22 03:10

som-snytt


The underscore in Scala indicates an existential type, i.e. an unknown type parameter, which has two main usage:

  • It is used for methods which do not care about the type parameter
  • It is used for methods where you want to express that one type parameter is a type constructor.

A type constructor is basically something that needs a type parameter to construct a concrete type. For example you can take the following signature.

def strangeStuff[CC[_], B, A](b:B, f: B=>A): CC[A] 

This is a function that for some CC[_] , for example a List[_], creates a List[A] starting from a B and a function B=>A.

Why would that be useful? Well it turns out that if you use that mechanism together with implicits and typeclasses, you can get what is called ad-hoc polymorphism thanks to the compiler reasoning.

Imagine for example you have some higher-kinded type: Container[_] with a hierarchy of concrete implementations: BeautifulContainer[_], BigContainer[_], SmallContainer[_]. To build a container you need a

trait ContainerBuilder[A[_]<:Container[_],B] { 

def build(b:B):A[B]

}

So basically a ContainerBuilder is something that for a specific type of container A[_] can build an A[B] using a B.

While would that be useful ? Well you can imagine that you might have a function defined somewhere else like the following:

def myMethod(b:B)(implicit containerBuilder:ContainerBuilder[A[_],B]):A[B] = containerBuilder.build(b)

And then in your code you might do:

val b = new B()
val bigContainer:BigContainer[B] = myMethod(b)
val beautifulContainer:BeautifulContainer[B] = myMethod(b)

In fact, the compiler will use the required return type of myMethod to look for an implicit which satisfies the required type constraints and will throw a compile error if there is no ContainerBuilder which meets the required constraints available implicitely.

like image 26
Edmondo1984 Avatar answered Oct 17 '22 03:10

Edmondo1984


That's useful when you deal with instances of parametrized types without caring of the type parameter.

trait Something[A] {
  def stringify: String
}

class Foo extends Something[Bar] {
  def stringify = "hop"
}

object App {
  def useSomething(thing: Something[_]) :String = {
    thing.stringify
  }
}
like image 39
Stephane Godbillon Avatar answered Oct 17 '22 03:10

Stephane Godbillon