Differences between using underscores in these scala methods

What is the difference and name of the term between these underscores from these codes: (see the section handler(resource) )

1.

 def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = { val resource = new java.io.FileInputStream(f) try { val hh = handler(resource)_ hh(2) } finally { resource.close() } } val bs = new Array[Byte](4) readFile(new File("scala.txt")) { input => b: Byte => println("Read: " + (input.read(bs) + b)) } 

I got a compilation error:

 Error:(55, 29) _ must follow method; cannot follow Byte => T val hh = handler(resource)_ ^ 

What does it mean?

2.

 def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = { val resource = new java.io.FileInputStream(f) try { val hh = handler(resource) _ hh(2) } finally { resource.close() } } // Lower parts are same, so removed for brevity... // ... 

The result is the same as not. 1, I got: _ must follow method compilation error.

I read this because the underscore is used to convert the method to a function ( ETA Extension ), but I also saw that the same underscore is used for the Partial Application Function without a problem, for example:

 val sum = (x: Int, y: Int) => x + y val sum2 = sum _ 

In this case, there is no error.

3.

 def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = { val resource = new java.io.FileInputStream(f) try { val hh = handler(resource)(_) hh(2) } finally { resource.close() } } //... 

It works well. If I'm not mistaken, the underscore in this case is called the ETA extension , is that right? But I also read from this Q / A , this underscore is for Partial Application Functions . On the same page, someone also said that this is Placeholder syntax. So which one is correct?

4.

 def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = { val resource = new java.io.FileInputStream(f) try { val hh = handler(resource) hh(2) } finally { resource.close() } } //... 

This character does not use underscores, but it works just as well. 3. My question is for this case, what is the difference with no. 3? Should I use no. 4 than not. 3? Is underlining redundant to none. 3?

Sorry for the many questions here, but this underlined thing is really confusing.

I once thought that the complexity of underscores in Scala corresponds to the complexity of the pointer and link (* / & / & &) in C / C ++.

UPDATE:

5.

I again found something interesting in underlining:

 scala> def sum(x: Int, y: Int) = x + y // sum is a method because of def sum: (x: Int, y: Int)Int scala> val sum2 = sum _ // sum2 is explicit ETA expanded function from sum sum2: (Int, Int) => Int = <function2> scala> sum2(2,3) // testing res0: Int = 5 scala> val sum3 = (x: Int, y: Int) => x + y // sum3 is a function object sum3: (Int, Int) => Int = <function2> scala> val sum4 = sum3 _ // what happpened here? sum4: () => (Int, Int) => Int = <function0> scala> sum4()(2,3) res2: Int = 5 

Could you tell me what happened to sum4 ? Why sum3 _ result sum3 _ have a function type: () => (Int, Int) => Int ?

6.

 List(1, 2, 3) foreach println _ 

According to this answer, these are Partially applied functions . Well, I see that the space before the underline is quite complicated. This is actually the same as:

 List(1, 2, 3).foreach(println(_)) 

So this is really a partially applied function.

But if I did this:

 scala> List(1, 2, 3).foreach(println _+1) //#1 <console>:8: error: type mismatch; found : Int(1) required: String List(1, 2, 3).foreach(println _+1) ^ scala> List(1, 2, 3).foreach(println _+"#") //#2 printed out nothing (why?) scala> List(1, 2, 3).foreach(println 1+_) //#3 <console>:1: error: ')' expected but integer literal found. List(1, 2, 3).foreach(println 1+_) ^ scala> List(1, 2, 3).foreach(println "#"+_) //#4 <console>:1: error: ')' expected but string literal found. List(1, 2, 3).foreach(println "#"+_) ^ 

A novice will usually consider the underscore in this case to be a placeholder, but I think this is not so, right?

+6
source share
1 answer

1 and 2 are the same, and this is eta-expand , which means that a function is transformed from just a function as part of the language to the real object of a certain FunctionN class:

 scala> def f(a: Int) = a f: (a: Int)Int scala> f.apply(1) <console>:9: error: missing arguments for method f; follow this method with `_' if you want to treat it as a partially applied function f.apply(1) ^ scala> f _ res1: Int => Int = <function1> scala> (f _).apply(1) res2: Int = 1 

In your example, this does not work, since handler(resource) is an expression that returns a Byte => T object object (since handler is a FileInputStream => Byte => T object function, and you partially applied it), so scala cannot do eta extensions for expressions (only for values ​​and methods).

4 is partially used as a side effect of supporting scala curry functions ( curried i means the ability to measure parameters one by one).

3 is clearly explicitly partially applied .

Note that in all three examples, your handler: FileInputStream => Byte => T function handler: FileInputStream => Byte => T is an object (therefore, it is already extended) if you are trying to do the same using methods with several parameters (which are not yet expanded to a curry function) - you get opposite results for 1 & 2 & 4:

 scala> def f(a: Int)(b: Int) = a //it not a curried function, as it just multi-parameter-list method f: (a: Int)(b: Int)Int scala> f(2) <console>:9: error: missing arguments for method f; follow this method with `_' if you want to treat it as a partially applied function f(2) ^ scala> f(2) _ //you need to convert f(2) to object first res4: Int => Int = <function1> scala> f(2)(_) res5: Int => Int = <function1> scala> f _ //expand method to the function object res6: Int => (Int => Int) = <function1> 

Thus, a partial application also makes the eta extension for you. You can also think of the eta extension as a (not exact) function with 0 partially applicable arguments, so these are pretty close terms, since partially applied functions are always objects in scala (in haskell it is a first-class function ), because you always need partially the applied function is first-class (for example, an object or an fc function) to apply it after an eta extension.

5 . scala can do eta extensions for the values ​​themselves, since they can be considered as compile-time functions with 0 parameters (which is why you see () => ... ). It can extend any value for a function object:

 scala> val k = 5 k: Int = 5 scala> val kk = k _ kk: () => Int = <function0> scala> val kkk = kk _ kkk: () => () => Int = <function0> scala> 

In your example, value is another object object. In addition, (Int, Int) => Int does not fully work in curry (the function takes some values ​​according to some estimates), but scala can also perform automatic partial application for such purposes. To make it fully charged:

 scala> def f(a: Int, b: Int) = a f: (a: Int, b: Int)Int scala> (f _).curried res23: Int => (Int => Int) = <function1> scala> def f(a: Int, b: Int)(z: Int) = a f: (a: Int, b: Int)(z: Int)Int scala> (f _).curried res22: Int => (Int => (Int => Int)) = <function1> 

This process is actually called currying.

Another way to make it in curry is to use tuples. It's not as clean as currying actually removes tuples, but scala Tuple is just a class and not a tuple in the parameter list: (Int, Int) => Int - the input is not a tuple in scala terminology, but in ((Int, Int)) => Int input tuple (regardless of what FP-perspecive is, a tuple of objects in the first case and a tuple of one object in the second). An example of pseudo-tuping:

  scala> def f(a: Int, b: Int) = a f: (a: Int, b: Int)Int scala> (f _).tupled res24: ((Int, Int)) => Int = <function1> 

5 vs 1 & 2 As you have already seen, you cannot apply the eta extension to an expression, only the / values ​​/ vars methods:

  scala> 5 _ <console>:8: error: _ must follow method; cannot follow Int(5) 5 _ ^ scala> val (a, b) = (5, 5) scala> (a + b) _ <console>:10: error: _ must follow method; cannot follow Int (a + b) _ ^ 

You see the “method” required in the error message, but scala aims to handle methods / values ​​/ vars (when they are members of a class / object) in the same way (at least partially) with UAP support.

6 This is the eta extension that returns Function0 by default:

 scala> val a = println _ a: () => Unit = <function0> 

Here you can expect function1, but println overloaded, and the eta extension mechanism selects the smallest signature. When another type is expected (for example, in Function1 in foreach ) - it can choose another:

 scala> val a: String => Unit = println _ a: String => Unit = <function1> 

As I said, you can consider an object function as a function partially applied with 0 arguments (which includes an eta extension, if necessary), so there is a source of confusion with another answer (I would choose the best example there).

As stated in PS2, this extension can be applied automatically:

 scala> List(1,2) foreach println 1 2 

About println _ +"#" - it works because any class (including Function1 ) in scala has implicit def + (s: String) (therefore, Int does not work here) defined in Predef (see SI-194 ):

 scala> println _ res50: () => Unit = <function0> scala> println _ + "#" res51: String = <function0># 

All other parameters do not work due to 5 vs 1 & 2 , in fact, scala cannot even parse a line after a one-parameter function:

 scala> println "#" <console>:1: error: ';' expected but string literal found. println "#" ^ 

You must specify the host object to fix it, since scala expects something like "obj method param" (but this is an experimental function, and sometimes you need to insert a few empty lines or ";" to make it work):

 scala> Predef println "aaa" aaa 

PS About C ++ reference / pointer. A function does not matter as a compile-time structure; therefore, the compiler simply creates a value for it; this process is called eta-expand (or this abstraction for pure functionality). This value can be part of a pointer (an object with a link to it) or just a reference to it - it does not matter. The fact is that here the function moves from compilation (method) to the runtime (fc-function), so it "comes to life".

PS2. Sometimes scala does this extension automatically (for example, here ) when a partially applied method with several parameters is explicitly passed as a parameter.

PSN You can find more information about underscores in @Daniel C. Sobral's answer about scala prefixes.

+7
source

Source: https://habr.com/ru/post/980400/


All Articles