How to get generic (polymorphic) lambda in scala?

P∀scal is a compiler plugin that provides more concise syntax for encoding polymorphic values as objects with a generic method.

The identity function, as a value, has type ∀A. A => A. To translate that into Scala, assume a trait

trait ForAll[F[_]] {
  def apply[A]: F[A]
}

Then the identity function has type ForAll[λ[A => A => A]], where I use the kind-projector syntax, or, without kind-projector:

type IdFun[A] = A => A
type PolyId = ForAll[IdFun]

And now comes the P∀scal syntactic sugar:

val id = Λ[Α](a => a) : PolyId

or equivalently

val id = ν[PolyId](a => a)

("ν" is the Greek lowercase letter "Nu", read "new")

These are really just shorthands for

new PolyId {
  def apply[A] = a => a
}

Multiple type parameters and parameters of arbitrary kinds are supported by P∀scal, but you need a dedicated variation on the above ForAll trait for each variant.


I really like @Travis Brown 's solution:

import shapeless._

scala> Poly(identity _)
res2: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$1$2$@797aa352

-

scala> def f[T](x: T) = x
f: [T](x: T)T

scala> Poly(f _)
res3: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$2$2$@664ea816

-

scala> def f[T](l: List[T]) = l.head
f: [T](l: List[T])T

scala> val ff = Poly(f _)
ff: shapeless.PolyDefns.~>[List,shapeless.Id] = fresh$macro$3$2$@51254c50

scala> ff(List(1,2,3))
res5: shapeless.Id[Int] = 1

scala> ff(List("1","2","3"))
res6: shapeless.Id[String] = 1

Poly constructor (in some cases) will give you eta-expansion into Shapeless2 Poly1 function, which is (more-less) truly generic. However it doesn't work for multi-parameters (even with multi type-parameters), so have to "implement" Poly2 with implicit + at approach (as @som-snytt suggested), something like:

object myF extends Poly2 {
  implicit def caseA[T, U] = at[T, U]{ (a, b) => a -> b}
}

scala> myF(1,2)
res15: (Int, Int) = (1,2)

scala> myF("a",2)
res16: (String, Int) = (a,2)

P.S. I would really want to see it as a part of language.


Only methods can be generic on the JVM/Scala, not values. You can make an anonymous instance that implements some interface (and duplicate it for every type-arity you want to work with):

trait ~>[A[_], B[_]] { //exists in scalaz
  def apply[T](a: A[T]): B[T]
}

val f = new (List ~> Id) {
  def apply[T](a: List[T]) = a.head
}

Or use shapeless' Poly, which supports more complicated type-cases. But yeah, it's a limitation and it requires working around.