Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversions for defs/lambdas in Scala?

I just ran into a strange disparity between functions and objects (scala 2.10):

implicit def conv(c: Int => String) : (PrintStream => Int => Unit) = p => v => p.println(c(v))
def f(h: PrintStream => Int => Unit) : Unit = h(System.out)(1)

def a(x: Int) = x.toString
val b = (x: Int) => x.toString

//    def main(args: Array[String]) = f(a) // fail
//    def main(args: Array[String]) = f((x: Int) => x.toString) // fail
def main(args: Array[String]) = f(b) // ok

Why is there a difference between defs/lambda literals and lambda vals?

Update: apparently, the Problem does not occur for binary functions: Implicit conversion of a function to a second-order-function only works if the function to convert has at least two parameters

I checked this, and indeed the following code works:

implicit def conv(c: (Int,Unit) => String) : (PrintStream => Int => Unit) = p => v => p.println(c(v,()))
def f(h: PrintStream => Int => Unit) : Unit = h(System.out)(1)

def a(x: Int, y : Unit) = x.toString
val b = (x: Int, y : Unit) => x.toString

def main(args: Array[String]) = f(a) // ok
def main(args: Array[String]) = f((x: Int, y: Unit) => x.toString) // ok
def main(args: Array[String]) = f(b) // ok

Likewise, Nullary functions don't pose a problem, either:

implicit def conv(c: () => String) : (PrintStream => Int => Unit) = p => v => p.println(c())
def f(h: PrintStream => Int => Unit) : Unit = h(System.out)(1)

def a() = "1"
val b = () => "1"

def main(args: Array[String]) = f(a) // ok
def main(args: Array[String]) = f(() => "1") // ok
def main(args: Array[String]) = f(b) // ok

So, rephrasing the question: why does this not work for UNARY methods and functions?

Update: the problem also seems to be related to the target type (the type of f's argument h). The following also works (this time, in favour of "eta-expansion counts as hop", because we need to create a method value from a using _)

implicit def conv(c: Int => String) : Unit = ()
def f(h: Unit) : Unit = System.out.print("?")

def a(x: Int) = x.toString
val b = (x: Int) => x.toString

def main(args: Array[String]) = f(a _) // ok
def main(args: Array[String]) = f((x: Int) => x.toString) // ok
def main(args: Array[String]) = f(b) // ok
like image 659
Björn Karge Avatar asked Feb 11 '15 09:02

Björn Karge


1 Answers

In scala defs are methods and are diffrent from functions.

scala> def a( x: Int, y: Int ): Int = x + y
a: (x: Int, y:Int)Int

scala> (x: Int, y: Int) => x + y
res0: (Int, Int) => Int = <function2>

You can convert a method to function by partially applying it.

scala> b _
res1: (Int, Int) => Int = <function2>

So.. you can do,

implicit def conv(c: Int => String) : (PrintStream => Int => Unit) = p => v => p.println(c(v))
def f(h: PrintStream => Int => Unit) : Unit = h(System.out)(1)

def a(x: Int) = x.toString
val af = a _

def main( args: Array[ String ] ) = f( af )

Alse, as @srgfed01 mentioned in his comment... for the second case the problem is that... they types are not explicitly specified, if you specify the type correctly... the second case will work.

scala> f( ( a => a.toString ): (Int => String) )
1

or

scala> f( ( _.toString ): (Int => String) )
1

Now, about differences between methods and functions...

You can call a method taking no arguments without parenthesis ()... but you can not call a function without ().

scala> def g() = 5
g: ()Int

scala> g
res15: Int = 5

scala> () => 5
res13: () => Int = <function0>

scala> res13
res14: () => Int = <function0>

scala> res13()
res15: 5

One of the most important reasons for methods being different from functions is because creators of Scala wanted seamless inter-interoperability with Java without being stuck with Java's limitations.

So methods (def) are very much similar to Java methods and keeping functions different from methods enabled them with limitless freedom to create Scala, the way they wanted.

Also... Another major difference is that methods can accept Type-classes where as functions can not. Basically you can have generic methods like

scala> :paste

trait Behave {
    def behave
}

class A( elem: String ) extends Behave {
  def behave() {
    println( elem )
  }
}

// Exiting paste mode, now interpreting.

defined trait Behave
defined class A

Now you can define a generic method,

scala> def check[ T <: Behave ]( t: T ): Unit = t.behave()
check: [T <: Behave](t: T)Unit

But you can not define a function like this,

scala> ( t: T ) => t.behave()
<console>:8: error: not found: type T
          ( t: T ) => t.behave()

or like this

scala> ( t: (T <: Behave) ) => t.behave()
<console>:1: error: ')' expected but '<:' found.
   ( t: (T <: A) ) => t.behave()
like image 84
sarveshseri Avatar answered Oct 23 '22 03:10

sarveshseri