开发者

Scala currying with 'byname' parameters and non-'byname' parameters

开发者 https://www.devze.com 2023-02-16 13:05 出处:网络
I have (had) an assumption that appears to be wrong.My \'main\' language in the past has been C++ and in C++ I could do function parameter binding like this in pseudo:

I have (had) an assumption that appears to be wrong. My 'main' language in the past has been C++ and in C++ I could do function parameter binding like this in pseudo:

// declaration
void f(int param1, int param2);

// usage
func<void, int> boundfunc = bind(f, 1, _1)
func(2) // equivalent to f(1, 2)

The interesting bit is that if I replace 1 in the definition of boundfunc with a function call, then that function call is invoked at the bind site, not the call to func.

So, when trying to solidify the concept of currying in my head, I compare it with binding in C++ and I assume (incorrectly) that currying with a non-'byname' parameter would equate to the same idea - called at the 'bind site'. Of course, this isn't what's happening, and I'm curious as to why f and g below behave the same. I'm obviously missing something...

import scala.util.Random

class X { println("Constructing"); val r = new Random }

def f(limit: Int)(x: X) = x.r.nextInt(limit)
def g(limit: Int)(x: => X) = x.r.nextInt(limit)

def r = f(100)(new X) // Doesn't print "Constructing"
def s = g(100)(new X) // Doesn't print "Constructing"

r // prints "Constructing"
r // prints "Constructing"

s // also prints "Constructing"
s // also prints "Constructing"

I expect the definition of s to do what it's doing (i.e. not call 'new X') but I did expect the definition of r to call it due to the fact that f has a basic definition of the x parameter (i.e. non-'byname').

Now, as I said, there's clearly some basic FP concept I'm missing, which I hope to rectify soon, but I'd also like to know if there's a way to get the equivalent of parameter binding so that new X isn't called repeatedly.

(Edit after answer from barjak)

Indeed, the problem stems from my misuse of def in the creation of r and s - I should be using val, which makes the call to new X happen at the 'bind site'.

The last surprising thing is that the 'byname' parameter in g is resolved only once, when we create s, as opposed to when we evaluate s explicitly. (i.e. "Constructing" is only printed when we define r and s as opposed to when we define r and when we evaluate s).

Now, my assumption is that Scala's doing the properly defined thing and binding x to th开发者_Go百科e result of the second parameter and thus it doesn't matter, in the context of s or r whether x is 'byname' or not.

I suppose, if I wanted to evaluate x repeatedly, I would need more than a 'byname' parameter, and would require an explicit function parameter instead, which would evaluate repeatedly when evaluating s.


r is declared with the keyword def, which actually means "reevaluate the expression for each call". I think you want to use the val keyword, which will evaluate the expression only once for each instanciation of X.

val r = f(100)(new X)
val s = g(100)(new X)

Otherwise, I think your interpretation is right.


As far as I understood by name parameters there is no great difference between f and g in

def f(limit: Int)(x: X) = x.r.nextInt(limit)
def g(limit: Int)(x: => X) = x.r.nextInt(limit) 

because of evaluating x in body definitions only once.

But if you had a loop or so inside definition x could be evaluated more than one ... and then in f a new X would be evaluated only one time (before method call) but in g a new X is evaluated as often as the loop runs.

/* using x in body twice (for demonstration only) */
def f(limit: Int)(x: X) = { x.r.nextInt(limit); x.r.nextInt(limit) }
def g(limit: Int)(x: => X) = { x.r.nextInt(limit); x.r.nextInt(limit) }
val r = f(100)(new X) /* prints "Constructing" only one time */
val s = g(100)(new X) /* prints "Constructing" twice */
r /* Doesn't print "Constructing" */
r /* Doesn't print "Constructing" */
s /* Doesn't print "Constructing" */
s /* Doesn't print "Constructing" */

And finnally to bring currying into play perhaps you would like to bind new X to x which you can do with a swap of parameter lists in function definition. Then you could do

def f(x: X)(limit: Int) = x.r.nextInt(limit)
val r = f(new X)_
val s = f(new X)_
r(100)
r(200)
s(100)
s(200)

where r and s are two different (independent) random streams constructed with currying f (no need of by name parameters).

0

精彩评论

暂无评论...
验证码 换一张
取 消