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

179 views
Skip to first unread message

Simon Schäfer

unread,
Oct 9, 2012, 5:15:26 AM10/9/12
to scala-l...@googlegroups.com
I just asked this question on SO, but because I think I didn't get the
correct answer yet I try my luck here.

The question:
http://stackoverflow.com/questions/12778963/what-is-class-a-useful-for

In short: Why does the type parameter [_] compile?

scala> class A[_]
defined class A

scala> trait X { def a[_](x: Any) }
defined trait X

Is there a use case for it? I can't see anyone, because it is not
possible to refer to it:

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 = _ }
^

Lars Hupel

unread,
Oct 9, 2012, 6:27:53 AM10/9/12
to scala-l...@googlegroups.com
The answers on SO are not actually relevant to the question, which you
correctly observed, but apparently nobody believed you.


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 })#λ]

Simon Schäfer

unread,
Oct 9, 2012, 9:09:48 AM10/9/12
to scala-l...@googlegroups.com
Nice answer, thanks! But there are some things I still do not understand:

Is `type F[_]` not a higher kinded type? Why does `type F[_] = Int`
compile whereas `type F[_] = List` does not? On the other hand with
`trait HKT[F[_]]` it's the different way. `type X = HKT[Int]` does not
compile whereas `type X = HKT[List]` does. What is the difference?

`const0("")` compiles fine and has type `const0.Out[String]` but is a
type alias for Int. Are there use cases, in which one can take use of
the parameterization to String?

FlyingDog is a functor, right? With a type lambda, how does the
implementation of `swoosh` look when there is no type parameter that can
be applied to `f` or is it impossible to use such a type (is this what
you meant with "[...]except that there's no real use case")?

class X
new FlyingDog[({type λ[α] = X})#λ]{ def swoosh[A,B](f: A => B, a: X): X
= ??? }

Paul Phillips

unread,
Oct 9, 2012, 9:24:25 AM10/9/12
to scala-l...@googlegroups.com
It's always interesting to see people come up with elaborate answers to questions like this (I say that genuinely) but if you ask me, there are only two possibilities: a) it is a bug b) it should be a bug.

If it is not a bug, then explain this:

scala> class A[_, _]
<console>:7: error: _ is already defined as type _
       class A[_, _]
                  ^

But what that really tells you is that it is simply an artifact of the implementation and _ as a type parameter should be prohibited.  Even if there were some legitimate use case, it should be prohibited anyway.  There's no reason in the world to add another obscure way to use an underscore.

Lars Hupel

unread,
Oct 9, 2012, 9:43:41 AM10/9/12
to scala-l...@googlegroups.com
> Is `type F[_]` not a higher kinded type?

type F[X] = … // has kind '* -> *'
type F[_] = … // also has kind '* -> *'

Both are really the same thing.

(Be cautious with the term 'higher-kinded'. A simple kind like '* -> *'
doesn't qualify as 'higher', unless you say that a function of type 'T
=> T' is a higher order function.)

> Why does `type F[_] = Int` compile whereas `type F[_] = List` does
> not?

The syntax `type F[X] = …` declares a type alias. After the `=`, a
regular type is expected. `List` is not a type by itself.

When trying to figure out what is allowed after `type F[_] = `, it's the
same. Just a regular type.

> On the other hand with `trait HKT[F[_]]` it's the different way.
> `type X = HKT[Int]` does not compile whereas `type X = HKT[List]`
> does. What is the difference?

Now, `F[_]` is a type parameter. The `[_]` indicates that `F` must be a
type constructor of kind `* -> *`. `List` is such a type constructor,
`Int` is not.

(The trait `HKT` can be said to be higher-kinded, since it's kind is (*
-> *) -> *.)

Let me try to elaborate. Assume you want to refactor this:

trait HKT[F[_]]

to using type members.

trait HKT {
type F[_]
}

Extending the former:

class Unicorn extends HKT[Option]

Extending the latter:

class Unicorn extends HKT {
type F[X] = Option[X] // observe that `X` appears twice
}

(I do understand that the difference is very subtle. Feel free to ask
further questions.)

> `const0("")` compiles fine and has type `const0.Out[String]` but is a
> type alias for Int. Are there use cases, in which one can take use of
> the parameterization to String?

I don't understand the question.

> FlyingDog is a functor, right?

Right.

> With a type lambda, how does the implementation of `swoosh` look when
> there is no type parameter that can be applied to `f` or is it
> impossible to use such a type [...]
>
> class X
> new FlyingDog[({type λ[α] = X})#λ]{ def swoosh[A,B](f: A =>
> B, a: X): X = ??? }

No, it's possible.

You already simplified the types. Indeed, the functor for the constant
type `X` is trivial:

scala> trait Z
defined trait Z

scala> new FlyingDog[({ type λ[α] = Z })#λ] {
def swoosh[A,B](f: A => B, a: Z): Z = a
}
res14: java.lang.Object with FlyingDog[[_]Z] = $anon$1@24ebb33

> (is this what you meant with
> "[...]except that there's no real use case")?

No. The construct `class A[_]` has no use case for the following reasons:

1) type lambdas exists

2) A[T₁] and A[T₂] are treated as completely disparate types, although
they're "morally" equivalent:

scala> implicitly[A[Int] =:= A[String]]
<console>:9: error: Cannot prove that A[Int] =:= A[String].

Contrast that with:

scala> type λ[_] = A
defined type alias λ

scala> implicitly[λ[Int] =:= λ[String]]
res16: =:=[λ[Int],λ[String]] = <function1>


Lars Hupel

unread,
Oct 9, 2012, 9:44:40 AM10/9/12
to scala-l...@googlegroups.com
> If it is not a bug, then explain this:
>
> scala> class A[_, _]
> <console>:7: error: _ is already defined as type _
> class A[_, _]
> ^

I think only this is a bug. `_` as type parameter "name" should be
treated the same way as `_` as value parameter "name".

Paul Phillips

unread,
Oct 9, 2012, 10:07:59 AM10/9/12
to scala-l...@googlegroups.com


On Tue, Oct 9, 2012 at 6:44 AM, Lars Hupel <hu...@in.tum.de> wrote:
I think only this is a bug. `_` as type parameter "name" should be
treated the same way as `_` as value parameter "name".



By disallowing it, you mean? Which means they're both bugs.  You can't declare a value parameter named '_'.

scala> class A { def f(_: String): String = "" }
<console>:1: error: identifier expected but '_' found.
       class A { def f(_: String): String = "" }
                       ^
<console>:1: error: ':' expected but '}' found.
       class A { def f(_: String): String = "" }
                                               ^

scala> class A(_: String) { }
<console>:1: error: identifier expected but '_' found.
       class A(_: String) { }
               ^

Lars Hupel

unread,
Oct 9, 2012, 10:10:00 AM10/9/12
to scala-l...@googlegroups.com
I just came up with another analogy.

> trait HKT[F[_]]

… is similar to

abstract class HOF(f: Int => String)


> trait HKT {
> type F[_]
> }

… is similar to

trait HOF {
def f(x: Int): String
}

Now, implementing the former:

class Unicorn extends HOF((x: Int) => x.toString)

or, equivalent:

class Unicorn extends HOF(_.toString)

(This closely resembles `HKT[Option]`.)


Implementing the latter:

class Unicorn extends HOF {
def f(x: Int): String = x.toString
}


Lars Hupel

unread,
Oct 9, 2012, 10:16:41 AM10/9/12
to scala-l...@googlegroups.com
> By disallowing it, you mean? Which means they're both bugs. You can't
> declare a value parameter named '_'.

Oh, right. I completely forgot that. I only looked at

(_: Int, _: String) => "foo"

But the point about too many uses of the dreaded underscore is fair
enough. Maybe disallowing `type F[_]` and friends is a good idea.

Paul Phillips

unread,
Oct 9, 2012, 10:22:36 AM10/9/12
to scala-l...@googlegroups.com


On Tue, Oct 9, 2012 at 7:16 AM, Lars Hupel <hu...@in.tum.de> wrote:
Oh, right. I completely forgot that. I only looked at

(_: Int, _: String) => "foo"

Which is, funnily enough, a completely different use of the underscore (placeholder syntax), "underscoring" the point.

Simon Schäfer

unread,
Oct 9, 2012, 3:09:07 PM10/9/12
to scala-l...@googlegroups.com
Hallo Florian,

meine drei Terminwünsche:

Mi 9:45-11:15
Do 9:45-11:15
Fr 14.00-15:30

Grüße
Simon

Som Snytt

unread,
Oct 9, 2012, 5:06:09 PM10/9/12
to scala-l...@googlegroups.com
On Tue, Oct 9, 2012 at 6:24 AM, Paul Phillips <pa...@improving.org> wrote:
 there are only two possibilities: a) it is a bug b) it should be a bug.

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

Is it a bug if there is an issue?

Is there an issue if it no longer crashes?

If a compiler crashes in 2.9 and there's no one there to hear it, etc.

Simon Schäfer

unread,
Oct 9, 2012, 5:40:30 PM10/9/12
to scala-l...@googlegroups.com
First, sorry of my last mail, it doesn't belong here. ;)
Ok, that makes sense. Thanks for explanation.
>
>> `const0("")` compiles fine and has type `const0.Out[String]` but is a
>> type alias for Int. Are there use cases, in which one can take use of
>> the parameterization to String?
> I don't understand the question.
I find it confusing that `const0("")` compiles and that it represents
the same type as `const0(0)`. Both `const0.Out[String]` and
`const0.Out[Int]` are type aliases for Int (and it turn out that both
are completely equal).

scala> implicitly[const0.Out[Int] =:= const0.Out[String]]
res45: =:=[const0.Out[Int],const0.Out[String]] = <function1>

What is the sense of being able to parameterize Out with a type when it
is unnecessary in this case?
>
>> FlyingDog is a functor, right?
> Right.
>
>> With a type lambda, how does the implementation of `swoosh` look when
>> there is no type parameter that can be applied to `f` or is it
>> impossible to use such a type [...]
>>
>> class X
>> new FlyingDog[({type λ[α] = X})#λ]{ def swoosh[A,B](f: A =>
>> B, a: X): X = ??? }
> No, it's possible.
>
> You already simplified the types. Indeed, the functor for the constant
> type `X` is trivial:
>
> scala> trait Z
> defined trait Z
>
> scala> new FlyingDog[({ type λ[α] = Z })#λ] {
> def swoosh[A,B](f: A => B, a: Z): Z = a
> }
> res14: java.lang.Object with FlyingDog[[_]Z] = $anon$1@24ebb33
Ok, didn't think of that.

Simon Schäfer

unread,
Oct 9, 2012, 5:59:53 PM10/9/12
to scala-l...@googlegroups.com
I don't get this to compile with a complete implementation of a functor:

scala> :paste
// Entering paste mode (ctrl-D to finish)

abstract class Functor[A, B](f: A => B) {
def map(x: A): B // A and B here seem not to be correct, how to
represent F[A]?
}
def ListFunctor[A, B](f: A => B) = new Functor[List[A], List[B]](f) {
def map(x: List[A]): List[B] = x map f
}

// Exiting paste mode, now interpreting.

<console>:11: error: type mismatch;
found : A => B
required: List[A] => List[B]
def ListFunctor[A, B](f: A => B) = new Functor[List[A],
List[B]](f) {
^

Lars Hupel

unread,
Oct 9, 2012, 7:17:54 PM10/9/12
to scala-l...@googlegroups.com
Okay, I take it that the second analogy was not helpful for
understanding. What I intended to show is the following similarity:

type member with parameters ≡ methods with parameters

type parameter with parameters ≡ constructor parameter which is a
function taking parameters

> abstract class Functor[A, B](f: A => B) {
> def map(x: A): B // A and B here seem not to be correct, how to represent F[A]?
> }

You cannot define the interface of a functor that way. Sorry for
misleading you.

Lars Hupel

unread,
Oct 9, 2012, 7:20:57 PM10/9/12
to scala-l...@googlegroups.com
> I find it confusing that `const0("")` compiles and that it represents
> the same type as `const0(0)`. Both `const0.Out[String]` and
> `const0.Out[Int]` are type aliases for Int (and it turn out that both
> are completely equal).
>
> scala> implicitly[const0.Out[Int] =:= const0.Out[String]]
> res45: =:=[const0.Out[Int],const0.Out[String]] = <function1>
>
> What is the sense of being able to parameterize Out with a type when it
> is unnecessary in this case?

Because the interface, in that case `Function`, dictates that the return
type depends on the input type. Our constant function has to obey, but
since the return type is actually constant, it ignores the input type
and simply returns `Int`.

Som Snytt

unread,
Oct 9, 2012, 7:47:48 PM10/9/12
to scala-l...@googlegroups.com
On Tue, Oct 9, 2012 at 4:17 PM, Lars Hupel <hu...@in.tum.de> wrote:
Okay, I take it that the second analogy was not helpful for
understanding. What I intended to show is the following similarity:

type member with parameters ≡ methods with parameters


FWIW, I got the analogy, but maybe because I was, just, thinking it.

It made think why can't I evolve my API this way to remove a value parameter:

def f(@deprecated @deprecatedName(`arg) _: Int, s: String) = ???

as the equivalent of adding def f(s: String) and deprecating the first form.

Is this scala-debate fodder?  Scala-musings?

Simon Schäfer

unread,
Oct 9, 2012, 7:55:58 PM10/9/12
to scala-l...@googlegroups.com
Ok, that is clear now. But to fully understand this topic I need to do
some exercises. Hopefully here are a lot of them:
http://stackoverflow.com/questions/4415511/scala-type-programming-resources

Interested in copying your first mail as answer to SO, so that I can
accept it?

Lars Hupel

unread,
Oct 10, 2012, 2:06:19 AM10/10/12
to scala-l...@googlegroups.com
> Interested in copying your first mail as answer to SO, so that I can
> accept it?

You can do that if you wish (I don't have an account over there), but
please include a link to this thread.

Simon Schäfer

unread,
Oct 10, 2012, 7:45:26 AM10/10/12
to scala-l...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages