Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add lazy parameters #240

Closed
scabug opened this issue Nov 14, 2007 · 19 comments
Closed

add lazy parameters #240

scabug opened this issue Nov 14, 2007 · 19 comments

Comments

@scabug
Copy link

scabug commented Nov 14, 2007

For now, we only have call-by-name parameters. We should allow the lazy modifier on parameters, for instance:

def f[a](lazy t: a) = {
 ..
}

The meaning should be equivalent to:

def f[a](t': => a) = {
  lazy val t: a = t'
  ..
}
@scabug
Copy link
Author

scabug commented Nov 14, 2007

Imported From: https://issues.scala-lang.org/browse/SI-240?orig=1
Reporter: @dragos
Attachments:

@scabug
Copy link
Author

scabug commented Apr 4, 2008

@lrytz said:
see also #720

@scabug
Copy link
Author

scabug commented Oct 14, 2010

Matthew Pocock (drdozer) said:
I think someone in IRC said that someone else had a patch that provided a prototype of this functionality. Is this right, or am I confused by hear-say?

Either way, I would like to help out if I can with implementing this. My particular use-case is in building lazy, infinite case-class structures such as this rather boring one:

case class Foo(lazy data: Foo)

I presume that any patch that adds lazy function parameters would out of the box, or with minor tweaking, support this also.

@scabug
Copy link
Author

scabug commented Nov 2, 2010

@hubertp said:
Since I handled the previous lazy val bugs I will try to fix that one as well in my spare time.

@scabug
Copy link
Author

scabug commented Nov 2, 2010

Matthew Pocock (drdozer) said:
What would the semantics of lazy constructor arguments be on pattern matching?

case class Foo(lazy data: Foo)

foo match {
case Foo(data) => // forces evaluation of lazy data
case Foo(_) => // has data been evaluated here? I hope not!
}

The second case needs to not evaluate data. If it does, then it will be impossible to pattern-match against (portions of) infinite data structures.

@scabug
Copy link
Author

scabug commented Nov 3, 2010

@jrudolph said:
I gave it a try:

jrudolph/scala-starrless@65052ce

It's still rough:

  • Works only for def parameters
  • (as suggested) introduces a new name for the parameter, which is the easiest way but not the best when thinking of named parameters
  • No error condition checking, like mixing of lazy/by-name

@scabug
Copy link
Author

scabug commented Nov 3, 2010

@jrudolph said:
The preliminary version of the patch

@scabug
Copy link
Author

scabug commented Nov 29, 2011

@paulp said:
For completeness, see also duplicate #5244.

@scabug
Copy link
Author

scabug commented Nov 29, 2011

@paulp said:
Oh, I guess I'm not used to jira doing something useful. Automatically generated bidirectional links? What's next, useable search? A comment system which exceeds telegard in sophistication? The mind reels.

@scabug
Copy link
Author

scabug commented May 2, 2013

Ryan Hendrickson (ryan.hendrickson_bwater) said:
Does anything block somebody productively championing this (writing a SIP, updating the patch, delivering an impassioned plea to Martin, waiting for Martin to finish x other things so that he has time to consider this class of thing)?

Additional desired syntax, if this is supported in (primary) constructors:

class Stuff([[mods]] lazy val a: Int, [[mods]] def b: Int)

translating to (modulo named-argument issues):

class Stuff(a': => Int, b': => Int) {
  [[mods]] lazy val a: Int = a'
  [[mods]] def b: Int = b'
}

@scabug
Copy link
Author

scabug commented May 2, 2013

@retronym said:
Shaking out the corner cases would be the first step.

To get the ball rolling: what would be expected here?

def foo(lazy a: Int)(b: Int = a) { a }
foo({println("!"); 0})()

@scabug
Copy link
Author

scabug commented May 2, 2013

Matthew Pocock (drdozer) said (edited on May 2, 2013 9:07:32 PM UTC):
Leaving aside for a moment that default values don't make sense in anonymous functions, I'd expect these two to be (operationally) semantically equivalent modulo currying:

def foo(lazy a: Int)(b: Int = a) { println("<") ; a; println(">") }
def bar(lazy a: Int): (Int) => Unit = (b: Int = a) => {println("<") ; a; println(">"); ()}

With the 'evaluate on first de-reference' semantics of lazy, I'd expect this to run as follows:

foo({println("!"; 42})()
| !<>
foo({println("!"; 42})(13)
| <!>

However, I'd see an argument for excluding lazy arguments from default values lists, as this least-surprise semantics will require some fancy re-writes that I can't imagine how to get right in the presence of currying.

@scabug
Copy link
Author

scabug commented May 2, 2013

Ryan Hendrickson (ryan.hendrickson_bwater) said:
Why is that hard to get right? Just as

def foo(a: Int)(b: Int = a) { ... }

foo(effectfulCode)()

rewrites to (I forget the exact mangling, but roughly)

def foo(a: Int)(b: Int) { ... }
def foo$default2(a: Int): Int = a

val x$1: Int = effectfulCode
foo(x$1)(foo$default2(x$1))

, I would expect

def foo(lazy a: Int)(b: Int = a) { ... }

foo(effectfulCode)()

to rewrite to

def foo(a: => Int)(b: Int) { ... }
def foo$default2(a: => Int): Int = a

lazy val x$1: Int = effectfulCode
foo(x$1)(foo$default2(x$1))

Wouldn't that have the desired semantics?

@scabug
Copy link
Author

scabug commented May 3, 2013

@retronym said:
Given that to make make that case work, we have to perform the rewriting at the call site, should we do it there in all cases? One disadvantage: changing => A to lazy A would require recompilation of client code (although it would appear to be binary compatible.)

We've been burned so often by unforseen (although not unforseeable) feature interactions (think Value Classes). So I'm naturally cautious with this sort of thing.

@scabug
Copy link
Author

scabug commented May 3, 2013

Ryan Hendrickson (ryan.hendrickson_bwater) said:
Not sure what you mean by ‘do it there in all cases’; my assumption was that any time we would rewrite the call site to use vals to capture arguments, if the parameter in question was marked lazy, we'd capture its argument with a lazy val instead. Did you have additional cases in mind?

It is a good point about the binary-but-not-really compatibility of a function taking => A and a function taking lazy A, but only if there are subsequent parameter lists and default arguments. My low-confidence guess is that practically, it wouldn't cause real issues that often—I at least am pretty used to having to recompile all client code regardless of the binary compatibility of dependencies, given how implicit resolution might change—but I defer to your experience.

@scabug
Copy link
Author

scabug commented May 3, 2013

@jrudolph said:
The "additional case" would be the original one where the rewriting was done in the method and not at the call site.

@scabug
Copy link
Author

scabug commented May 3, 2013

Ryan Hendrickson (ryan.hendrickson_bwater) said:
Ahhh, I see. Mmm. I don't know; it's not as if it's incorrect to have the rewriting at the call site when necessary and also in the method in all cases, just a bit wasteful.

Also, consider eta-expansion:

def foo(lazy a: Int) = { a; a }
val f = foo _

f should have type (=> Int) => Int (I think; unless we're reforming how call-by-name types work), but the lazy semantics should be preserved so that a is evaluated only once. Either the rewrite is internal, or the eta-expansion gets complicated.

Similarly, can we allow anonymous functions to have lazy arguments, given that we already permit them to have call-by-name?

val f: (=> Int) => Int = (lazy a) => { a; a }

@scabug
Copy link
Author

scabug commented May 3, 2013

Ryan Hendrickson (ryan.hendrickson_bwater) said:
Also-also, that call-site rewriting really screws with type inference, so I'd really rather not introduce it into any new cases if we don't have to.

@SethTisue
Copy link
Member

I believe this is now out of scope for Scala 2, but there's a Scala 3 ticket: lampepfl/dotty-feature-requests#174

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants