Sometime I stumble into the semi-mysterious notation of
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
in Scala blog posts, which give it a "we used that type-lambda trick" handwave.
While I have some intutition about this (we gain an anonymous type parameter A
without having to pollute the definition with it?), I found no clear source describing what the type lambda trick is, and what are its benefits. Is it just syntactic sugar, or does it open some new dimensions?
Type lambdas are vital quite a bit of the time when you are working with higher-kinded types.
Consider a simple example of defining a monad for the right projection of Either[A, B]. The monad typeclass looks like this:
Now, Either is a type constructor of two arguments, but to implement Monad, you need to give it a type constructor of one argument. The solution to this is to use a type lambda:
This is an example of currying in the type system - you have curried the type of Either, such that when you want to create an instance of EitherMonad, you have to specify one of the types; the other of course is supplied at the time you call point or bind.
The type lambda trick exploits the fact that an empty block in a type position creates an anonymous structural type. We then use the # syntax to get a type member.
In some cases, you may need more sophisticated type lambdas that are a pain to write out inline. Here's an example from my code from today:
This class exists exclusively so that I can use a name like FG[F, G]#IterateeM to refer to the type of the IterateeT monad specialized to some transformer version of a second monad which is specialized to some third monad. When you start to stack, these kinds of constructs become very necessary. I never instantiate an FG, of course; it's just there as a hack to let me express what I want in the type system.
type World[M[_]] = M[Int]
causes that whatever we put inA
inX[A]
theimplicitly[X[A] =:= Foo[String,Int]]
is always true I guess.The benefits are exactly the same as those conferred by anonymous functions.
An example usage, with Scalaz 7. We want to use a
Functor
that can map a function over the second element in aTuple2
.Scalaz provides some implicit conversions that can infer the type argument to
Functor
, so we often avoid writing these altogether. The previous line can be rewritten as:If you use IntelliJ, you can enable Settings, Code Style, Scala, Folding, Type Lambdas. This then hides the crufty parts of the syntax, and presents the more palatable:
A future version of Scala might directly support such a syntax.
To put things in context: This answer was originally posted in another thread. You are seeing it here because the two threads have been merged. The question statement in the said thread was as follows:
Answer:
The one underscore in the boxes after
P
implies that it is a type constructor takes one type and returns another type. Examples of type constructors with this kind:List
,Option
.Give
List
anInt
, a concrete type, and it gives youList[Int]
, another concrete type. GiveList
aString
and it gives youList[String]
. Etc.So,
List
,Option
can be considered to be type level functions of arity 1. Formally we say, they have a kind* -> *
. The asterisk denotes a type.Now
Tuple2[_, _]
is a type constructor with kind(*, *) -> *
i.e. you need to give it two types to get a new type.Since their signatures do not match, you cannot substitute
Tuple2
forP
. What you need to do is partially applyTuple2
on one of its arguments, which will give us a type constructor with kind* -> *
, and we can substitue it forP
.Unfortunately Scala has no special syntax for partial application of type constructors, and so we have to resort to the monstrosity called type lambdas. (What you have in your example.) They are called that because they are analogous to lambda expressions that exist at value level.
The following example might help:
Edit:
More value level and type level parallels.
In the case you have presented, the type parameter
R
is local to functionTuple2Pure
and so you cannot simply definetype PartialTuple2[A] = Tuple2[R, A]
, because there is simply no place where you can put that synonym.To deal with such a case, I use the following trick that makes use of type members. (Hopefully the example is self-explanatory.)