Pre-SIP Higher kinded generics improvements

Summary

This Pre-Sip suggests some improvements for generics and higher kinded types:

  • Type partial application
  • Curried types
  case class Bar[F[_]](...)

  def func[F[_, _]] : Bar[F[A, *]]
  def func[F[_][_]] : Bar[F[A]] 

Motivation

As of now it is easy to express a type parameter with one argument set:

def func[F[_,_]](...) : ...

But it cannot be curried even if language has curried types:

type FCurried = [A] =>> [B] =>> F[A, B]

This inconsistency leads to more verbose syntax:

// Current
def func[F[_, _]] : Bar[[Value] =>> F[A, Value]]
// Proposed
def func[F[_, _]] : Bar[F[A, *]]
// or
def func[F[_][_]] : Bar[F[A]] 

An example from cats library:

def func[F[_], Error](using MonadError[F, Error])(...) : F[A]
// instead of
def func[F[_] : MonadError[*[_], Error], Error](...) : F[A]

This significantly decreases code readability.

Solution

As a solution I suggest these changes:

  • Allow many argument sets for generic types:
    case class Bar[F[_]](...)
    
    def func[F[_][_]] : Bar[F[A]] 
    
  • Allow partial type application:
    case class Bar[F[_]](...)
    
    def func[F[_, _]] : Bar[F[A, *]] // The same as [Value] =>> F[A, Value] 
    
    And some more complex examples
    Foo[*[_]]
    Foo[*[+_]]
    Foo[*[-_]]
    Foo[*[+_, -_]] // same as [Value] =>> Foo[Value] where Foo expects F[+_, -_]
    Foo[*[_][_]] // same as [Value] =>> Foo[Value] where Foo expects F[_][_]
    
    But it seems that usually compiler will be able to figure kind out itself so I’m not sure the language needs these kind marks.

These improvements will improve code readability and bring types closer to the common values.

Given that things like [A] =>> [B] =>> F[A, B] already exist, is there any fundamental reason why we can’t define all n-ary type constructors as syntactic sugar for their curried equivalent?

1 Like

For now, classes can only have one type parameter list. I think it is because of java interoperability. Anyway scala in general has n-ary things by default.

1 Like

you can do this today:

//> using options -Xkind-projector

def func[F <: [_] => [_] => Any] : Bar[F[A]]
def func[F[_, _]] : Bar[F[A, *]]
2 Likes

Looking at Wildcard Arguments in Types (Migration Strategy #3), it looks like eventually we should be able to do exactly what you are suggesting using plain syntax.

At that point bar1 and bar2 in Scastie - An interactive playground for Scala. should be equivalent.

That page was last updated 2 years ago!

That examples compiles fine today if you add -Xkind-projector:underscores to the Scalac options.

1 Like

And here’s the scastie to prove it :slight_smile:

While I’m glad that we can opt into this useful capability, why does it need a special flag to enable?

As noted in Nov 2024, the documentation on this topic is confusing, outdated, and spread across two pages. So we see pre-SIPs for features that are partly implemented, yet the documentation is lacking, so even motivated programmers with an interest in the topic can’t figure out how the language works.

Probably this is the reason to break with Java…

To be fair, we’ll probably need a SIP for type lambda placeholders anyway to make them an official language feature.

Well, this happened historically, since it was first added just for cross-compiling with Scala 2 code that used kind-projector. Now, it wasn’t supposed to remain that way indefinitely, as you can see by the timelines in the doc pages, but so far it has.