1

I am trying to better understand how to do mass parsing/interpreting.

I am having difficulty understanding lines like these:

type L = () => Long

And then later there are other lines like

val term = chainl1(myNum, "[*/]".r ^^ {
  case "*" => (u1:L, u2:L) => () => u1() * u2()
  case "/" => (u1:L, u2:L) => () => u1() / u2()
})

I am just trying to understand the underlying structure here. What exactly is () here? What is the logical flow of these val statements?

I assume this means "if we find * or / in myNum, match on these two cases depending on which one it is, and then... long flow of logic I don't understand"

Peter Neyens
  • 9,770
  • 27
  • 33

3 Answers3

4
type L = () => Long

describes a function with zero arguments and return type Long. For example

def someConstant() = 5L

Here someConstant is of type L.

This line

case "*" => (u1:L, u2:L) => () => u1() * u2()

will return you a function of type Function2 (arity 2) with return type Function0(arity 0) which when is called will return the result of u1() * u2().


val f = () => (x:Int) => x

can be written as

() => ((x: Int) => x)

Here f is a function with arity 0 which when is called returns another function with arity 1 which accepts Int argument and returns it when called.

 f()(6) == 6

is true statement.

ka4eli
  • 5,294
  • 3
  • 23
  • 43
  • So what is a line like "case "*" => (u1:L, u2:L) => () => u1() * u2()" saying? – user5127720 Jul 17 '15 at 14:00
  • I'm still not quite sure I understand. It says "case *" and then somehow extracts two numbers -- is it saying that u1 and u2 must be of type L, or are they being "cast" to L? And then why the extra => () => part? Why the u1() and u2()? – user5127720 Jul 17 '15 at 14:09
  • scala is not that language you can learn by looking through code examples, if you are doing that. You need to read a book, go though tutorials etc. It often has much more complex structures than this, and you will be lost there if you won't prepare yourself – Archeg Jul 17 '15 at 14:10
  • 1
    @Archeg I learn best through example. Reading abstract tutorials and books has never helped me. I need hard examples to understand how things are being put together. I can figure out the abstraction after the fact. – user5127720 Jul 17 '15 at 14:12
  • @user5127720 just look at the code example in the answer here for example: http://stackoverflow.com/questions/23261082/scala-macro-to-print-code and decide for yourself whether you can learn scala by example. If you still want to do that, I wish you luck :) – Archeg Jul 17 '15 at 14:18
  • Showing some complex code (where even comments in the thread mention how unreadable they are) that is well above the complexity of this particular code question is not a strong example, I would say. I'm not interested in getting into a debate, though -- I just want to understand this example for now. – user5127720 Jul 17 '15 at 14:20
  • 1
    It doesn't extract two numbers. In case of "*" it returns a function that accepts two arguments of type L. This function needs to be called to do anything, and you need to pass two values of type L, which is itself functions. So this case returns a function that accepts two functions and provides a result of type Long. – Archeg Jul 17 '15 at 14:25
  • What is each piece doing though, I mean? I understand the overall gist but not the pieces. "=> (u1:L, u2:L)" versus "=> ()" versus "=> u1() * u2()" – user5127720 Jul 17 '15 at 14:27
3

The answer of ka4ell is correct, but does not mention why you would use () => Long instead of just using Long: with () => Long or L the execution of the calculation which returns the Long is delayed. We only execute the functions at the moment we want the actual result, this is called lazy evaluation.

In your case:

case "*" => 
  // Return a function which takes two Ls : u1 and u2
  // and which returns an L : () => u1() * u2()
  (u1:L, u2:L) => () => u1() * u2()

Let's define a simple function which returns an L :

// return a function which will return a Long
// this could potentially be a very expensive calculation
def giveMeAnL(n: Long) = () => {
  println("giveMeAnL is called")
  n
}

If we would use an analogous function than the one in the case:

// analogous case "*" => ...
def multiply(u1:L, u2:L) = () => {
  println("multiply is called")
  u1() * u2()
}

// create two Ls
val (l1, l2) = (giveMeAnL(5L), giveMeAnL(2L))

val productL = multiply(l1, l2) // productL is of type L

The value productL now contains an L which can calculate the product of the two values. At this point, neither the product nor the two values are calculated. If we call the productL value, the two values are calculated and the product of these values is calculated.

val product = productL()
// multiply is called
// giveMeAnL is called
// giveMeAnL is called
// product: Long = 10

If somewhere in your parsing steps, you want to ignore some L values, the results of these Ls are never calculated, which improves the performance.

case "multiply first with 5" => 
  (u1:L, u2:L) => () => u1() * 5L // u2 is never executed
Peter Neyens
  • 9,770
  • 27
  • 33
  • I tried replacing L with some other arbitrary letter like X to see if this was a general thing, and it gave me errors. Does this whole thing only work with Long? Is L somehow shorthand for some native type? It also seemed to execute when I removed the L's from the 5 and 2. I'm still struggling to understand what the heck type L = () => Long is doing and why we'd want it – user5127720 Jul 17 '15 at 14:59
  • I just used the type `L`, since that was what you were using, it is no native type. – Peter Neyens Jul 17 '15 at 15:00
  • Try replacing L with another letter like X, and it suddenly stops working: val productX = multiply(giveMeAnX(5X), giveMeAnX(2X)) //Invalid literal number – user5127720 Jul 17 '15 at 15:01
  • The `L` after `5L` and `2L` is an unlucky consequence. It works without the `L` because the `5` and `2` `Int`s here are turned into `Longs` – Peter Neyens Jul 17 '15 at 15:04
  • So adding an L after a number IS something native to Scala and unrelated to this question? It's typecasting it to Long, but this is unnecessary because this is done automatically in the call to giveMeAnX due to the argument type "(n: Long)"? – user5127720 Jul 17 '15 at 15:05
  • Adding the `L` of `l` after a number to make it an explicit `Long` is unrelated to this question yes, in Java you can do the samething. In Scala an `Int` will be automatically converted to a `Long` when a `Long` is expected yes. – Peter Neyens Jul 17 '15 at 15:09
  • It might have been clearer if I had replaced the type alias `L` with `type DelayedLong = () => Long`, but I wanted to stay close to your code. – Peter Neyens Jul 17 '15 at 15:12
  • So "def giveMeAnL(n: Long) = () =>" implies that if we do "l1 = giveMeAnL(5)" we're only storing a function in l1 with an argument 5. So if we do l1() then we actually excute the body of giveMeAnL with argument 5. The "type" of l1 is therefore () => Long (a function returning a Long), which is why we use L to represent this? I could technically then replace L with "() => Long" everywhere, including the function body of multiply()? – user5127720 Jul 17 '15 at 15:15
  • What if we just wanted to make giveMeAnL a function accepting one argument? Like one where it doesn't have 5 "stored" inside it, but rather something where later we can do l1(5) and have it invoke giveMeAnL(5)? Would this be giveMeAnL(n:Long) = (Long) => { n } and then later l1 would be of type "(Long) => Long"? – user5127720 Jul 17 '15 at 15:20
  • I'm not sure what you mean exactly. If you want to take a parameter you can write a function like `multiply`, eg `def squared(u1: L) = () => u1() * u1()`. – Peter Neyens Jul 17 '15 at 16:18
0

If we split the line case "*" => (u1:L, u2:L) => () => u1() * u2():

case "*" => means match * to the code that is written next

(u1:L, u2:L) => {} is a definition of function that has two arguments of type L

u1 and u2 are functions, because their type is L, and it is actually () => Long which means this is a function that takes nothing and returns Long

() => u1() * u2() is a function with no arguments, that calls u1 and u2 and multiplies their results Going back: (u1:L, u2:L) => () => u1() * u2() is a function of two arguments which returns a function of no arguments, that executes both arguments of a first function, multiplies them and return the result

So, () => means a function with no arguments. The only exception here is case * => where => doesn't belong to a function, but rather to match statement

Archeg
  • 8,364
  • 7
  • 43
  • 90
  • I guess the main hiccup for me is this idea of a function "accepting no arguments" suddenly branching into something that seems to accept arguments. For instance I don't understand how we go from () to u1() and u2(). Aren't u1 and u2 "arguments"? Something just seems needlessly complicated here. We've got a function definition of two functions returning a no-argument function returning a multiplier of two functions that correspond to a type that return no-argument functions yielding longs? What? – user5127720 Jul 17 '15 at 15:11
  • `u1` and `u2` are the arguments of the parent function. The child function just has closure around them. Small example, assuming `L = Int`. Let's say you have `val parentF = (u1:L, u2:L) => () => u1 * u2`. Then you take `val childF = parentF(2, 3)`. `childF` is still a function, but it has no arguments. And now it looks kinda like `() => 2 * 3` (it's not precise, but I think it helps to understand it). What happens here is that `childFunction` when it was created, remembered the references to `u1` and to `u2`, and this behavior is called closure. Just remember that `L = () => Long` in reality – Archeg Jul 17 '15 at 15:19
  • So childFunction is a function accepting no arguments because the arguments were already encapsulated in parentF when we made the assignment? Now any time we deal with childF we are implicitly dealing with parentF(2,3) specifically? – user5127720 Jul 17 '15 at 15:29
  • In other words childF references parentF(2,3), and having two arguments 2,3 here is fine because parentF is a function of form (u1:Int, u2:Int), which returns a function, which, when invoked, will multiply u1 and u2. So now if I output childF(), it will output 6? Am I understanding the flow right? – user5127720 Jul 17 '15 at 15:32
  • childFunction is a function accepting no arguments because it is defines with `() => ...` But it actually requires `u1` and `u2` and cannot work without them. It is called closure, because a function that requires `u1` and `u2`, but does not have them is called `opened` (if I correctly mention the term). So it is closed by having a parent function which has `u1` and `u2`. And I still recommend you to start reading a book :) Just look at the size of the discussion you've triggered – Archeg Jul 17 '15 at 15:32
  • Alright, I'll concede the point on this one. Do you have any books you'd recommend? – user5127720 Jul 17 '15 at 15:33
  • @user5127720 yes, you do understand it correctly. But remember that my example was about L = Int, and you have L = () => Long, so you need to count that – Archeg Jul 17 '15 at 15:34
  • http://www.scala-lang.org/documentation/books.html I think that the first one (by Martin) is great, and he is the Scala's author I think (It's also free if you read it online). But I have read only his book, so no opinion on the others. P.S. his book is also built around code examples, so it might fit your learning style – Archeg Jul 17 '15 at 15:35
  • Well if you understand all this stuff and that's the only book you've read on the subject, either it's a damn good book, or you're a genius (or both). I'll give it a shot. Thanks! – user5127720 Jul 17 '15 at 15:37