Default-Value Type Parameter and Named Type Arguments

372 views
Skip to first unread message

Chris Hodapp

unread,
May 26, 2012, 12:11:06 AM5/26/12
to scala-...@googlegroups.com
There is demand, it would seem, from myself and from others for supporting named type arguments and default values for type parameters. I was asked by Chris Vogt (my GSOC mentor) whether I would be willing to look at implementing these features, and I said I would.

I plan to write a SIP that specifies how these should work, should an agreement on the issue be reached here.

My current idea for basic naming support is something like this for naming would look something like:
"""
class A[T, U]

val a1 = new A[Int, String]         // Call normally
val a2 = new A[T = Int, U = String] // Provide names to make meaning clear
val a3 = new A[U = String, T = Int] // Reverse names, still works
val a4 = new A[Int, U = String]     // Provide as many position-resolved arguments as desired before providing name-resolved arguments
"""

My current idea for basic default support would look like:
"""
class B[T = String]

val b1 = new A[String] // Explicitly provide the default type
val b2 = new A[Int]    // Provide a different type
val b3 = new A         // Infers B[String] instead of B[Nothing]
"""

Of course, they should be able to interact, since they have synergistic benefits:
"""
class C[T = String, U = Int]

val c1 = new String[U = Char, T = Char]  // Can use the names
val c4 = new String[String, U = Int]     // Still uses positional resolution until named arguments start
val c5 = new String[U = Char]            // Default: T = String
val c6 = new String[Char]                // Default: U = Int
"""

It should also be possible to use default parameter values with type bounds, variance annotations, etc.:
"""
class D[T = List[_] <: Traversable[_]]

val d1 = new D[Seq[_]] // Works because Seq[_] <: Traversable[_]
val d2 = new D         // T defaults to List[_]
val d3 = new D[Int]    // Error


class E[+T = Traversable[_]]

val e1 = new T                       // T defaults to Traversable[_]
val e2: E[Traversable] = new E[List] // Variance
val e3: E[Any] = new E               // Right side T defaults to Traversable[_], but it doesn't matter, since it's cast to Any
"""

Note that even though all the examples so far have been with classes, methods should also work.

What I would most appreciate is help in identifying important nonobvious cases and deciding what to do in them.

One that I know about:
"""
def mimic[T = Any](x: T): T = x

mimic("abc") // What's mimic's return type?
"""

It is not entirely clear what we should do here. Normally (without a specified default value), we would infer that T is String in this case. However, it is not clear whether this should take precedence over the default value. I currently think that the default values should have the lowest-possible precedence, as this seems to be the idea that has been used with default parameter values for regular method parameters. In short, I think we should go ahead and infer String in this case. However, I'm not particularly attached to the idea of it working in this way if there is some good reason that it would be less surprising to have more complicated precedence rules.

Tony Morris

unread,
May 26, 2012, 12:19:14 AM5/26/12
to scala-...@googlegroups.com
OMG I totally want this.

Please keep the scalaz list informed of progress and if you need help. I
am sure there are others for whom this would be incredibly beneficial.
--
Tony Morris
http://tmorris.net/


Chris Hodapp

unread,
May 26, 2012, 12:38:40 AM5/26/12
to scala-...@googlegroups.com, tmo...@tmorris.net
I reposted this on the Scalaz list. I asked for discussion to be here, since I don't like the idea of two parallel discussions on the same topic going on.

Paul Phillips

unread,
May 26, 2012, 12:40:03 AM5/26/12
to Chris Hodapp, scala-...@googlegroups.com

On Fri, May 25, 2012 at 9:11 PM, Chris Hodapp <clho...@gmail.com> wrote:
I plan to write a SIP that specifies how these should work, should an agreement on the issue be reached here.

The key feature in my view is not one you mentioned, and which has no analogue in value parameters.  Personally I would gladly live without (as in - I'm not sure I even want) the features which closely parallel existing name/defaults.  Type parameter names are usually meaningless or nearly so: I'd rather know they're not being reordered.  Though defaults would have their uses.

But the thing which hits me over and over is the inability to specify a single type parameter without having to specify the others.  If I could name the one which needs naming - and ideally, if any I specified drove the type inference - then we'd have something.

Chris Hodapp

unread,
May 26, 2012, 12:46:41 AM5/26/12
to scala-...@googlegroups.com, Chris Hodapp
I agree that type parameter names are normally meaningless, as they tend to be single letters (desperation to keep already-long lines in Java from getting longer, I suppose). I have long lamented this fact.

Be that as it may, unless I am wrong, the behavior you want emerges from the simultaneous use of named arguments and default values and is, in fact, the "synergistic benefit" that I mentioned in my first post.

Paul Phillips

unread,
May 26, 2012, 12:51:51 AM5/26/12
to Chris Hodapp, scala-...@googlegroups.com
On Fri, May 25, 2012 at 9:46 PM, Chris Hodapp <clho...@gmail.com> wrote:
Be that as it may, unless I am wrong, the behavior you want emerges from the simultaneous use of named arguments and default values and is, in fact, the "synergistic benefit" that I mentioned in my first post.

It doesn't, or at least you are leaving too much unsaid.  The situation I am interested in is: there are four type parameters.  None of them have default values.  I specify one.

In the analogous situation with value parameters, the other three would all have to have defaults.  I don't want any of them to have defaults.  I want them to be inferred.

Chris Hodapp

unread,
May 26, 2012, 12:53:10 AM5/26/12
to scala-...@googlegroups.com, Chris Hodapp
Actually, I just realized that your probably mean something like:
"""
def mapping[K, V](key: K, value: V) = key -> value

mapping[V=Any]("one", 1)
"""

In which case, YES! It needs to do this and I should have mentioned it.

Tony Morris

unread,
May 26, 2012, 1:12:00 AM5/26/12
to Chris Hodapp, scala-...@googlegroups.com

It's more like the "type lambda" syntax that many of us endure, whereby we partially apply a type constructor.

As for the thing I really want, that would be defaulting a type variable. I care not for names, nor their alleged utility.

case class State[S,F[_]=Id,A](run: S => (A,S))

Yes please!

As for Paul's issue (which is also.many others'), how can I easily get the type constructor that is State with S=Int, but A is not yet applied? That is, a unary type constructor.

Tom Switzer

unread,
May 26, 2012, 1:16:17 AM5/26/12
to Chris Hodapp, scala-...@googlegroups.com
Looks nice. Only input I have is that default values with bounds should have the = after the bounds, so,

class D[T = List[_] <: Traversable[_]]

Would look like this instead:

class D[T <: Traversable[_] = List[_]]

To me this looks better, and also fits with default values being after the type as well.

Chris Hodapp

unread,
May 26, 2012, 1:24:36 AM5/26/12
to scala-...@googlegroups.com, Chris Hodapp
I agree. This is better.

Chris Hodapp

unread,
May 26, 2012, 1:38:51 AM5/26/12
to scala-...@googlegroups.com, Chris Hodapp
I think that the best you can do to get a partially applied type constructor is to do it the long way (i.e. manually define a method), since functions can't accept type parameters.

Miles Sabin

unread,
May 26, 2012, 5:17:46 AM5/26/12
to Chris Hodapp, scala-...@googlegroups.com
On Sat, May 26, 2012 at 5:53 AM, Chris Hodapp <clho...@gmail.com> wrote:
> Actually, I just realized that your probably mean something like:
> """
> def mapping[K, V](key: K, value: V) = key -> value
>
> mapping[V=Any]("one", 1)
> """
>
> In which case, YES! It needs to do this and I should have mentioned it.

Actually, in this case, I think I'd prefer to be able to define,

def mapping[K](key: K)[V](value: V) = key -> value

and then invoke,

mapping("one")[Any](1)

ie. support multiple type parameter blocks in the same way that
multiple value parameter blocks are currently supported, and allow the
two to be intermingled.

Of course you can encode this,

class Mapping[K](k : K] {
def apply[V](v : V) = k -> v
}
def mapping[K](k : K) = new Mapping(k)

But that's much clunkier than the intent seems to require.

Cheers,


Miles

--
Miles Sabin
tel: +44 7813 944 528
gtalk: mi...@milessabin.com
skype: milessabin
g+: http://www.milessabin.com
http://twitter.com/milessabin
http://underscoreconsulting.com
http://www.chuusai.com

Chris Hodapp

unread,
May 26, 2012, 5:48:29 AM5/26/12
to scala-...@googlegroups.com, Chris Hodapp
It seems to me that the changes I've proposed and the changes you've are proposed all move in the direction of unifying the rules for type and value parameters. I would say that this is a good direction to move in and making one change doesn't preclude the other. However, a slow-moving "baby steps" approach seems correct. In other words, let's not try to implement all of these things under a single proposal. Is there a consensus that allowing split (and possibly intermixed) parameter lists should be implemented before/instead of named type parameters? It feels to me that this takes power away from the generic's user without actually giving more to its writer (style isn't real power). It also forces you to curry your type parameters to get the full benefit of assigning them default arguments.

Chris Hodapp

unread,
May 26, 2012, 4:19:13 PM5/26/12
to scala-...@googlegroups.com
Here is updated, fixed, and expanded sample code:
"""
class A[T, U]

val a1 = new A[Int, String]         // Call normally
val a2 = new A[T = Int, U = String] // Provide names to make meaning clear
val a3 = new A[U = String, T = Int] // Reverse names, still works
val a4 = new A[Int, U = String]     // Provide as many position-resolved arguments as desired before providing name-resolved arguments


class B[T = String]

val b1 = new B[String] // Explicitly provide the default type
val b2 = new B[Int]    // Provide a different type
val b3 = new B         // Infers B[String] instead of B[Nothing]


class C[T = String, U = Int]

val c1 = new C[U = Char, T = Char]  // Can use the names
val c4 = new C[String, U = Int]     // Still uses positional resolution until named arguments start
val c5 = new C[U = Char]            // Default: T = String
val c6 = new C[Char]                // Default: U = Int


class D[T <: Traversable[_] = List[_]]

val d1 = new D[Seq[_]] // Works because Seq[_] <: Traversable[_]
val d2 = new D         // T defaults to List[_]
val d3 = new D[Int]    // Error
class E[+T = Traversable[_]]

val e1 = new T                             // T defaults to Traversable[_]
val e2: E[Traversable[_]] = new E[List[_]] // Variance
val e3: E[Any] = new E                     // Right side T defaults to Traversable[_], but it doesn't matter, since it's cast to Any

def mapping[K, V](key: K, value: V) = key -> value

mapping[V = Any]("one", 1) // Provide some type arguments while inferring the rest: Return a (String, Any)

Chris Hodapp

unread,
Jun 4, 2012, 6:14:03 AM6/4/12
to scala-...@googlegroups.com
The language changes that I need to make to complete my SOC project (of which this is one) are proving too radical (and interrelated, I am finding) to get approved through the standard community process within the timeframe stipulated by the SOC deadlines. Therefore, I'm going to be working in a fork. I will then deliver my work as an actual "project" at the end of the summer. Expect a report on how I'm doing on this list in about a month.


On Friday, May 25, 2012 11:11:06 PM UTC-5, Chris Hodapp wrote:

Peter

unread,
Jun 4, 2012, 1:17:38 PM6/4/12
to scala-debate
Chris,

you are not the first one thinking about default type parameters.
https://issues.scala-lang.org/browse/SI-3963?page=com.atlassian.jira.plugin.system.issuetabpanels:changehistory-tabpanel
was my suggestion but that wasn't the first, either. I've been told
that time that we can achieve the same or a similar effect by defining
a lower bound.

Btw, how would you resolve the following three function applications:

class A
class B extends A
class C extends B
def f[A=A](a: A = new B): A = a
f()
f(0)
f(new C)

Peter

Chris Hodapp

unread,
Jun 4, 2012, 2:53:50 PM6/4/12
to scala-...@googlegroups.com
I'm thinking that I would call the A=A default a cyclic type reference, since it would shadow the class A on the right-hand side. I would imagine that this would be cause to throw an error.

Chris

Peter

unread,
Jun 4, 2012, 2:58:58 PM6/4/12
to scala-debate
> I'm thinking that I would call the A=A default a cyclic type reference,
> since it would shadow the class A on the right-hand side. I would imagine
> that this would be cause to throw an error.

Ok, that wasn't my point so let's replace it by
def f[T=A](a: T = new B): T = a

Chris Hodapp

unread,
Jun 4, 2012, 3:18:43 PM6/4/12
to scala-...@googlegroups.com
f(): return a new B as a B (resolve defaults for value parameters before type parameters, since this could potentially reduce the number of defaults you have to use)
f(0): return 0 as an Int
f(new C): return the new C as a C

Peter

unread,
Jun 4, 2012, 4:03:49 PM6/4/12
to scala-debate
Thanks, I agree.
Reply all
Reply to author
Forward
0 new messages