Partial and Implicit Generics

Most of the time, the Scala compiler is able to infer the generic type of some object or function for you. However, when it is unable to do so, it requires you to specify all of the generic arguments (since it will resolve the unknown generics to Nothing, which I think is still fine).

The problem is when you want to avoid writing out possibly long types, for instance suppose we have the following and a function.

class ParsecE[S <: Stream[_, _], U, E, +A](/*args*/)
def pure[S <: Stream[_, _], U, E, A](a: =>A): ParsecE[S, U, E, A]

Now suppose we’ve got this function call somewhere pure[S, U, E, ParsecE[S, U, E, A]](atom). This is a bit long for my liking… for pure(atom) the compile infers ParsecE[Nothing, Nothing, Nothing, ParsecE[S, U, E, A]] so we are forced to add those generics. But the compiler got the last parameter correct! What would be nice is if we could write something like this instead; pure[S=S, U=U, E=E](atom).

An additional way this problem could be solved is by implicit generics; This requires a bit more work to implement however. Let’s redefine ParsecE and pure like so

class ParsecE[+A][implicit S <: Stream[_, _], U, E](/*args*/)
def pure[A][implicit S <: Stream[_, _], U, E](a: =>A): ParsecE[A] //S, U, E is implicit for ParsecE

Then, if we have the implicit types of S, U, E defined somewhere, then the compiler can substitute them in, allowing us to write pure(atom) or pure[ParsecE[A]](atom).

So where would the compiler look for these implicit generics? Well the first option would be if the enclosing class or method had generics of the same name defined and they were implicit (you can see this in the new definition of pure in the return value). For example, the pure(atom) call is within a method of the class ExpressionParser[S <: Stream[_, _], U, E, A]. If this were defined as ExpressionParser[A][implicit S <: Stream[_, _], U, E] then that provides all three types and they fit the constraints.

Alternatively we could declare implicit types in the same scope as the pure(atom) call, like so;

implicit type S = Stream[String, Char]
implicit type U = Unit
implicit type E = Default

pure(atom) // A is inferable, that's no problem, then there is an implicit S, U, E in scope!
pure[ParsecE[A]](atom) // also valid, the S, U, E are implicitly available for both types!

Thoughts?

I think this has been proposed multiple times—together with multiple lists of type arguments and curried type applications, so that you could just write:

def pure[S <: Stream[_, _], U, E][A](a: => A)
pure[S, U, E]

This sort of thing exists in multiple languages and works typically rather well (my favorite example is Agda).

In fact, you can already define functions that are called like pure, though it’s extremely cumbersome.

I’m confused by your concrete example—is atom actually of type ParsecE[S, U, E, A], and pure actually returning ParsecE[S, U, E, ParsecE[S, U, E, A]]?

Implicit generics seem more confusing. First, in this example they help a bit, but they don’t seem so compelling.
Second, they seem very dangerous. Implicits are a very powerful and sometimes dangerous feature; implicits resolved by-name might be even more dangerous, especially since there is not much kind-checking that can go on. If you ever swap names of E and U, the compiler won’t notice right away.

To be sure, this sort of implicit (which shares only the name of Scala implicits and few superficial other features) exists for Haskell for value-level arguments (https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters), and I’ve only seen it criticized. Nobody yet proposed extending it to the type level.

2 Likes

PS To be sure: please don’t read my post as “IMHO no” but as “I see these issues, can you make a stronger case?” Maybe there’s a better example around!

multiple lists of type arguments and curried type applications

There is a standard pattern (at least among the Typelevel projects) for defining a polymorphic method that separates inferrable type parameters from non-inferrable ones, which is the primary use case as far as I can see. I don’t really buy the idea of default type arguments. Example here and a bit of explanation here.

Yeah, that’s what I thinking about:

I just didn’t have a reference handy! I only knew of when Paul Phillips introduced it by patching the parser to allow a[A][B] (somewhere on scala-internals, if you want to add a link), but your blog post is a more useful introduction!
I guess we agree it’d be nicer if Scalac supported this with less cumbersome syntax?

And as I wrote, the example doesn’t seem so compelling over currying, and there are concerns. If @J_mie6 can improve his proposal or argument, I’m listening. If not, it can be a learning experience.

You’re correct in that the type of atom is ParsecE[S, U, E, A], I’ll
reply more fully when I get home

Isn’t that solved in Dotty? In dotty I think you can use named argument syntax for type parameters.

Yeah, like that. :slight_smile:

Can you define that?

To recap, implicit value parameters are when you say “here’s a type; find me some value that matches the type and grab it.”

What would an implicit type parameter be?

1 Like

I acknowledge the problems with the implicit type parameters, and yes, they aren’t truly implicit as @nafg suggests rather by name substitution of some designated types. However, I’d say the compiler would have a fairly fast idea of what’s gone wrong as soon as the types no longer match up somewhere along the way, but it’ll be hard to produce meaningful error messages about it and is is fairly dangerous!

Is there any reason why curried type parameters themselves are not in the language yet? It seems to solve my problem almost entirely!

I think they are in Dotty, no?
Also I’m not sure if it was implemented other than in a paulp branch, but
being that you can call an apply method with just type parameters, there’s
a way to get curried type parameters just by having chained apply methods.

So yeah, this actually does work.

(Ammonite session, scala 2.12.1)

@ import scala.reflect.ClassTag 
import scala.reflect.ClassTag
@ object O { def apply[A] = new { def apply[B: ClassTag](implicit tagA: ClassTag[A]) = (implicitly[ClassTag[A]], implicitly[ClassTag[B]]) } } 
defined object O
@ O[Int][String] 
res9: (ClassTag[Int], ClassTag[String]) = (Int, java.lang.String)

Note that if O.apply would use a context bound or other implicit parameter, it wouldn’t compile, so I had to use the longer form for the ClassTag[A].

Also, obviously the inner apply's parameters aren’t going to help the compiler infer what A is (haven’t actually tested). (Not sure about dotty – I did read something about type inference looking further ahead but I don’t know what the context was.) So the usefulness seems to extend only to “group your explicit types here and your all-or-nothing potentially inferred types there.” So it doesn’t seem to support the use case of “this type can certainly be inferred, but that one can be either explicit or inferred.”