1

I would like to create a function with the following signature:

def myFunction[T](functionWithName: (String, => T)): T 

so that I can call it, e.g., like this: val x = myFunction("abc" -> 4 * 3). Tuple doesn't accept by-name parameter, however, so the signature above is invalid.

Inspired by this answer, I tried the following implicit conversion:

implicit class ByName[T](getValue: => T) extends Proxy {
  def apply(): T = getValue
  def self = apply()
}

def myFunction[T](functionWithName: (String, ByName[T])): T = {
  // do something
  functionWithName._2()
}

The implicit doesn't work in this case, however (unlike in the linked answer).

  • Why does the implicit conversion to ByName doesn't work?
  • How can I achieve the desired effect of calling myFunction("abc" -> 4 * 3) where 4 * 3 is passed by name?
Mifeet
  • 12,949
  • 5
  • 60
  • 108
  • 1
    Why a tuple, what is wrong with 2 parameters? – insan-e Oct 18 '17 at 18:31
  • 1
    @insan-e There will actually be multiple variants of myFunction. It can be called like `myFun("a" -> 1*2, "b" -> 3*4)` or `myFun("a" -> 1*2, "b" -> 3*4, "c" -> 5*6)`, etc. Because there may be many arguments, I would like to avoid any unnecessary syntax to keep it readable. – Mifeet Oct 19 '17 at 16:06
  • 1
    I see. You can't do that probably because [tuples](https://github.com/scala/scala/blob/v2.12.3/src/library/scala/Tuple2.scala#L19) have eager arguments. Only way I see is to make whole tuple by-name, `functionWithName: => (String, T)`... – insan-e Oct 19 '17 at 17:45
  • 1
    I like that, an elegant solution to my problem. It cannot be used in all contexts (e.g. when the String needs to be evaluated before T), but can be used in others. Would upvote if it was an answer. – Mifeet Oct 20 '17 at 23:20

2 Answers2

4

You can change the call-by-name parameter to a thunk.

def myFunction[T](functionWithName: (String,() => T)): T = functionWithName._2()

myFunction(("abc", () => 4 * 3)) // 12

or to get it working with the implicit, you just need to explicitly provide the type to myFunction :

myFunction[Int]("abc" -> 4 * 3) // 12
adrice727
  • 1,482
  • 12
  • 17
  • Yes, that is another option. Is there a way how to do without the unnecessary 2 parentheses and arrow operator, though? Any ideas why the implicit conversion doesn't work? – Mifeet Oct 19 '17 at 16:07
  • @Mifeet See my updated answer. The `implicit` works if you provide the type. – adrice727 Oct 19 '17 at 18:23
1

I have two proposals to implement this:

  • Make whole tuple by-name: functionWithName: => (String, T)

  • Make your own class:

    class MyTuple[A, B](a: A, b: => B) {
      def _1: A = a
      def _2: B = b
      def toTuple = a -> b // optional
    }
    

and have a custom implicit method somewhere, like -> for Tuple2 (see ArrowAssoc in Predef):

  implicit final class ArrAssoc[A](private val self: A) extends AnyVal {
    @inline def -->[B](y: => B): MyTuple[A, B] = {
      new MyTuple(self, y)
    }
  }

Then you could say this:

  val t = 1 --> { println("blah"); 5 }
  //t._2
  //t.toTuple

The b parameter should not be evaluated until you call t._2 Or even make toTuple imlicit conversion, so when Tuple2 is espected you can pass a MyTuple...

insan-e
  • 3,883
  • 3
  • 18
  • 43