SIP : Implicit argument type parameters

250 views
Skip to first unread message

Mikaël Mayer

unread,
May 6, 2014, 11:10:27 AM5/6/14
to scala...@googlegroups.com
Dear scala users,

I added a new SIP on automatic argument inference:


Basically, it would allow the programmer to remove the type of the arguments by inferring the type based on the name of the variable.

def takeBest(myLongClassName) ...

I did not write an implementation of that concept though. I would like to request your feedback before starting something like that.
Any feedback is welcome.
Mikaël

Nick Stanchenko

unread,
May 6, 2014, 12:24:18 PM5/6/14
to scala...@googlegroups.com
Hi,

Is there a benefit beyond saving characters? I would say replacing “def” with “de”, “val” with “vl” and “var” with “vr” would be more effective at that :)
Joking apart, I would also argue that this gives an advantage solely to those following particular naming schemes, and as we know, naming things is one of the hardest problems in CS. For example, I am sure many people call lists “xs” and not “list”. It is also a good practice to give meaningful names, especially in method arguments, especially in public APIs. Perhaps the percentage of non-worthy small methods with useless and tedious signatures varies from one domain to another, but it’s not something that ever really bothered me personally.

By the way, do you suggest
var i = _ // var i: Int = 0
as well? :)

As a side-note, I would be very pleased to have a SIP on inferring types of default arguments, as in
def meth(x = 4) = ... // def meth(x: Int = 4) = ...

Nick

Som Snytt

unread,
May 6, 2014, 12:51:52 PM5/6/14
to scala...@googlegroups.com
I had asked about inferring types from default args, and the reply was that it was considered and dismissed (because fraught with whatever frightens you about inference).

Inferring from an overridden method is still under -Y.

As a variant of the current proposal, I would go for inferring results:

def sorted     = ???  // not implemented
def sortByName = ***  // implementation supplied


Instead of making me remember which of the 50 or so collections API to use in sorting on a member, just supply the body marked as the "extended ellipsis".

Or has someone already written that macro?

I bet many algorithms would be amenable.  It would take "don't reinvent the wheel" to the level of the "self-driving car."

def findInBigOhLogN(v: V) = ***


--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mikaël Mayer

unread,
May 6, 2014, 3:29:31 PM5/6/14
to scala...@googlegroups.com
Hello,

Saving characters is one benefit (and saving 2 chars by amening StringFormat instead of "+...+" is an example where it is still an argument)
In this SIP there are two proposals. The first is for automatic inference (the one that infers the type from the argument name following the rule "should match the class name except for the case"), the other is about custom type inference.
Namely, in context, we could have
implicit type xs: List[Int]
import com.example.{obj: MyCustomTypeIWillUseOftenInMyCode}
implicit type i: Int
which would allow user to go from:
def myMethod(obj: MyCustomTypeIWillUseOftenInMyCode, xs: List[Int], i: Int) = ...
to
def myMethod(obj, xs, i) = ...
val f = (i,xs) => xs(i)
Not only it would increase readability if the naming convention is suitable, but it also would reduce the code in length.
I can run statistics to check how much we can win.

pande...@gmail.com

unread,
May 7, 2014, 10:52:51 AM5/7/14
to scala...@googlegroups.com
What would be the benefit over: def takeBest(my: LongClassName) ?

Haoyi Li

unread,
May 7, 2014, 1:50:20 PM5/7/14
to scala...@googlegroups.com
<bikeshedding> 
I'd prefer flipping this on its head: rather than making the arguments typeless and name-mangling to get types, I'd make them nameless:

def takeBest(: LongClassName):
  // status quo
  implicitly[LongClassName]
  // with shorthand
  ?[LongClassName]

Where the thing is implicit, and you only are allowed to have one variable of that type around. That restriction is not too bad, since if you have multiple variables of the same type you probably should give them separate names anyway. 

The advantage of doing it this way is that we already have a known mechanism for going from Type => Term (implicits) which we could re-use, as opposed to making a whole new mechanism for going from termName => Type, and they both accomplish roughly the same thing.

This will also allow you to avoid assigning dummy names to implicits which you never use, e.g.

def doStuff(i: Int)(implicit ec: ExecutionContext) = ...

in such cases, the names given to implicits are completely meaningless, and forcing programmers to give things meaningless names is =<. If we could use:

def doStuff(i: Int)(implicit: ExecutionContext) = ...

We would avoid that, and we could always access the term via implicitly[ExecutionContext] or ?[ExecutionContext] anyway

</bikeshedding>

Jan Vanek

unread,
May 7, 2014, 2:35:25 PM5/7/14
to scala...@googlegroups.com
On 07.05.2014 19:50, Haoyi Li wrote:
<bikeshedding> 
I'd prefer flipping this on its head: rather than making the arguments typeless and name-mangling to get types, I'd make them nameless:

def takeBest(: LongClassName):
  // status quo
  implicitly[LongClassName]
  // with shorthand
  ?[LongClassName]

Where the thing is implicit, and you only are allowed to have one variable of that type around. That restriction is not too bad, since if you have multiple variables of the same type you probably should give them separate names anyway. 

The advantage of doing it this way is that we already have a known mechanism for going from Type => Term (implicits) which we could re-use, as opposed to making a whole new mechanism for going from termName => Type, and they both accomplish roughly the same thing.

This will also allow you to avoid assigning dummy names to implicits which you never use, e.g.

def doStuff(i: Int)(implicit ec: ExecutionContext) = ...

in such cases, the names given to implicits are completely meaningless, and forcing programmers to give things meaningless names is =<. If we could use:

def doStuff(i: Int)(implicit: ExecutionContext) = ...


Or:
def doStuff(i: Int)(implicit _: ExecutionContext) = ...
like in:
val f = (_: Int) => 5

Mikaël Mayer

unread,
May 8, 2014, 4:35:16 PM5/8/14
to scala...@googlegroups.com
The benefit appears if you have to write multiple methods handling LongClassName:

implicit type {{my;their}: LongClassName}
def takeBest(my) =
def takeWorst(my) =
def compare(my, their) =
def import( their) =

Nick Stanchenko

unread,
May 8, 2014, 4:38:01 PM5/8/14
to scala...@googlegroups.com
type A = LongClassName
def takeBest(my: A) = ...
def takeWorst(my: A) = ...
def compare(my: A, their: A) = ...
def `import`(their: A) = ... // mind the ``s

Mikaël Mayer

unread,
May 8, 2014, 4:38:25 PM5/8/14
to scala...@googlegroups.com
Making the argument nameless is the dual idea. You got it :-)
<bikeshedding>
What if like in javascript, we could have no arguments and then access them using the special name $args?
def takeBest() {
  $args(0) .... ///LongClassName
}
</bikeshedding>

Joke apart, this SIP could be really handy in cases when there are multiple methods with the same parameters.

Adriaan Moors

unread,
May 9, 2014, 8:20:05 AM5/9/14
to scala-sips
This looks far too much like magic to me. I don't think we need to reduce the characters spent writing code, as they are dwarfed by the cost in brain/cpu cycles spent parsing/reading/understanding/compiling the resulting code. This change impacts so many areas, including type inference, incremental compilation, IDE support, binary compatibility...

On a more general note, please continue discussing SIPs (including this one) on this mailing list! This is the right venue to convince a SIP committee member to champion your proposal at the next SIP meeting (which is at ScalaDays). I apologize for not having done the greatest job in communicating what's going on with the SIP committee, mostly because we've been super busy with 2.11 (which focussed on removing rather than adding stuff -- simplification SIPs most welcome :-))

If you'd like to propose a SIP for inclusion in 2.12, please do so before ScalaDays!

Mikaël Mayer

unread,
May 12, 2014, 10:59:22 AM5/12/14
to scala...@googlegroups.com
Maybe I should split the SIP into two. The first part is indeed kindof magic - which I still promote. Type inference is also magic. For sure.
The thing is that it looks good. Next time you write some scala, at some point you might think about this SIP again. It will stay in your mind. "Why cannot have I automatic type inference for arguments based on their name ! At least I could use the same name for the same argument type everywhere". That's the real magic behind this SIP. Once you have this idea in mind, it is hard to remove it.

Implicit typing would not reduce the readability, no much more if it is well used than using meaningful function names.
Indeed, if variable names are well choosed, then their type is obvious. In eclipse, you still have CTRL+F2 to open the type of a variable.

It might introduce more CPU cycles for compilation only if it is used. I bet it will be used more often if implemented. Of course, its implementation would require a lot of changes in the compiler, the incremental compilation, the parser, the IDE plugin. But this is why a SIP is written and I'm defending the worth of such addition. At least the compiled code will be the same, the same way that import statement and implicits are not present anymore at compile time. So binary compatibility is not an issue.

Another application? Look at this DSL definition:

implicit type s,t,u,v: String

class RichString(s) {
def add t = s + t
def diff t = if(s.size < t.size) ....
def pump u v = s"$u($s)*$v"
def search t = s.indexOf(t)
}

compared with the readability of the usual (and I omitted the return type because we are all "used" to this magic).

class RichString(s: String) {
def add(t: String) = s + t
def diff(t: String) = if(s.size < t.size) ....
def pump(u: String, v: String) = s"$u($s)*$v"
def search(t: String) = s.indexOf(t)
}

I will not have the time to implement a prototype for the next Scaladays because I have to help a master thesis student in his project. I assume that you expect an implementation, right?
I could do it if someone who knows the scala compiler well could help me to find the parts to change. Do you know anyone who could help me?

Best regards

Jason Zaugg

unread,
May 12, 2014, 11:19:04 AM5/12/14
to scala...@googlegroups.com
Hi Mikaël,

I'm a maintainer of the compiler. I personally don't think this proposal has a realistic chance to make it into the mainstream version of Scala. We don't have much feature budget left in the spec or compiler implementation, and are in general looking to remove features that don't pay there weight, rather than admit new ones.

The closest thing that we have to this is an experimental option `-Yinfer-argument-types` which infers argument types from the overriden method. We're on the fence whether or not to keep this around or remove it from the compiler, and haven't considered is worthwhile to propose as a SIP. But I think it is more in the spirit of the language, using types from the context of a declaration to drive inference.

Another issue I have with this proposal is the conflict with anonymous function parameter inference:

  implicit type s: Int
  List("1", "2", "3").map(s /*Int or String?*/ => s.reverse)

In any case, if would like to try to implement these ideas, I think the best place to start would be looking at the implementation of Yinfer-argument-types. I'm happy to answer questions, but be aware that what you're proposing is a non-trivial change.

Regards,

-jason

Jason Zaugg
Software Engineer

Typesafe – Build reactive apps!
Twitter: @retronym


Nick Stanchenko

unread,
May 12, 2014, 11:26:56 AM5/12/14
to scala...@googlegroups.com
One thing that bothers me with regard to this proposal(s) is the transient nature of names. For example, with the following declarations there are so many ways to break the code just by renaming something:

import Predef.{ Int ⇒ Xyz }
def add(xyz) = xyz + 3

class Int1
def id(int1) = int1

As a huge fan of refactoring, I can see how tethering names to types (or the other way round) will really complicate the process. I’d rather be able to rename something 10 times a day without touching imports or `implicit type` declarations, than to save a few characters here and there once a week. Of course one can argue that the IDEs will catch up and do this for me, but aren’t they also generating case-class-like stuff in Java? Yet not many people here seem to find that acceptable.

Nick

Rex Kerr

unread,
May 12, 2014, 12:12:06 PM5/12/14
to scala...@googlegroups.com
On Mon, May 12, 2014 at 7:59 AM, Mikaël Mayer <mmika...@gmail.com> wrote:
Maybe I should split the SIP into two. The first part is indeed kindof magic - which I still promote. Type inference is also magic. For sure.
The thing is that it looks good.

It looks good if you pretend that you only have the best case to worry about.  Otherwise it looks increasingly bad, because you have all sorts of convenient short variables that you are not allowed to use (or you will confuse yourself and possibly the compiler).
 

Look at this DSL definition:

implicit type s,t,u,v: String

Uh-oh.  u and v are vectors.  t is a Double (for time--or, wait, is it a Long or a Date?).  s, I agree, is a string.
 

class RichString(s) {
  def add t = s + t
  def diff t = if(s.size < t.size) ....
  def pump u v = s"$u($s)*$v"
  def search t = s.indexOf(t)
}

Unless you can maintain extreme discipline in separating code into different files and can context-switch really fast (so you know what "v"  means this time), you ought to write this like

implicit type s, sa, sb : String
class RichString(s) {
  def add sa = s + sa
  def diff sa = if (s.size < sa.size) ...
  def pump sa sb = s"$sa($s)*$sb"
  def search sa = s indexOf sa
}

which is basically just asking for compiler support for Hungarian notation.

I am all for eliminating redundant typing and reading, but I would much rather have a more limited scope, like so:

class RichString(s: String) {
  type = String
  def add(t) = s + t
  def diff(t) = if (s.size < t.size) ...
  def pump(a,b) = s"$a($s)*$b"
  def search(template) = s indexOf template
}

That is, within a scope you can set exactly one default type.

But I am not sure even this is necessary.

Nonlocality is bad for comprehension.  I think we should avoid it, even if it does save a fair few characters.

  --Rex

Nick Stanchenko

unread,
May 12, 2014, 12:18:03 PM5/12/14
to scala...@googlegroups.com
class RichString(s: String) {
  type = String
  def add(t) = s + t
  def diff(t) = if (s.size < t.size) ...
  def pump(a,b) = s"$a($s)*$b"
  def search(template) = s indexOf template
}

By the way, I wonder why “_” is not a valid type name. Still, “__” is:

class RichString(s: String) {
  type __ = String
  def add(t: __) = s + t
  def diff(t: __) = if (s.size < t.size) ...
  def pump(a: __, b: __) = s"$a($s)*$b"
  def search(template: __) = s indexOf template
}

I am sure there are other discreet characters that can be used (e.g., “*”). I tried a Unicode non-breakable space the other day, but apparently it’s still considered a space character by the lexer. 

Rex Kerr

unread,
May 12, 2014, 12:23:33 PM5/12/14
to scala...@googlegroups.com
Well, the point is to save the two/three characters needed by : S.  Otherwise you just
  type S = String
--Rex

--

Nick Stanchenko

unread,
May 12, 2014, 12:26:39 PM5/12/14
to scala...@googlegroups.com
Yes, sure. I was just trying to say that even with the current syntax there may be approaches that are visually less heavy and more readable than “: S”, albeit with the same or comparable symbol count.

Nick

Som Snytt

unread,
May 12, 2014, 12:28:14 PM5/12/14
to scala...@googlegroups.com
The new signature would be:

def id(@deprecatedName('int) int1) = ???


> The first part is indeed kindof magic

A quick way to prototype this would be to construct a custom compiler instance with existing components:

import scala.tools.nsc.io.MindReader

new Global(settings) with MindReader

An interesting experiment would be to piggyback on Scalite and infer types from tabbed indentation.

You could have Ints at column 4, Strings at 8, etc.  Types would be immediately visible without squinting.  That would extend the careers of Scala professionals by ten years without any retraining or bifocals.

Not sure where function types would go.  Either all the way to the left or all the way to the right.

Higher-kinded types would naturally get indented by 2.

--

Som Snytt

unread,
May 12, 2014, 2:04:34 PM5/12/14
to scala...@googlegroups.com
> why “_” is not a valid type name

it's reserved for Nil match { case _: List[_] => "hi" }

but I've tried using a "multiscore" to align arrows in case blocks, where it counts as a var id, { case __________ => res }

I'll have to update my style guide on where to put the multiscore in { case ____ : List[____] => "hi" }  // space before colon, too

Someone asked on SO why this works:

class X[A: TypeTag] ; class Y[B: TypeTag] extends X

which makes me wonder if there's a clever use, but I don't see anything cleverer than a type member would be:

scala> abstract class X[A: TypeTag] { def f(a: A, i: Int): String }
defined class X

scala> class Y extends { implicit val t = typeTag[Int] } with X { def f(x, i) = (x * i).toString * i }
defined class Y

scala> new Y f (5,6)
res6: String = 303030303030

The idea is an implicit tag drives inference of A, and -Yinfer-argument-types provides relief when implementing a method.

Maybe in a context where you're implementing (many) anonymous classes it could save a few chars while avoiding an alias.

scala> object Z {
     | implicit val t = typeTag[Int]
     | val y = new X {
     |   def f(x, i) = (x * i).toString * i
     | }}
defined object Z

Sorry, I thought for a moment this was scala-debate. Or scala-chat.





--
Reply all
Reply to author
Forward
0 new messages