Basically how to interpret code that makes use of curly braces when calling functions with multiple parameter lists or code that makes use of {} in place of () when acceptable.
Basically, code that looks like this:
foo() { val a = "Hello" val b = "world" s"$a $b" }
or
foo() { (x:Int) => val y = x * 2 println(s"output is: $y") }
Or a snippet of code defining a controller method using the play framework:
Action.async(action.parser) { request => Logger.info("Calling action") action(request) }
Or some slightly modified code from a code base at work:
extractActiveMembershipFromRequest(membershipKey) { requesterId => ...some code here... }
Or another slightly modified code from work:
secure.hasRequiredRole(user, role) { submitRequest(user) }
These pieces of code all share the same common problem: I cannot quickly tell what the code is actually doing. Is the code executing an already defined function? because I can see the () which is usually use for function invocation or is it declaring a function; because I can also see {}, which on the other hand is mostly used for defining functions, methods and classes? or is it just a way to define callbacks? or is it making use of some control structures I have never seen before?
The nifty interpretation that recently occurred to me, helps with making these syntaxes less confusing. It makes it possible to easily reason about Scala code that takes the form above.
The crucial part of the interpretation relies on appreciating Expressions.
And basically what are expressions?
An expression can be loosely defined as any piece of code, that contains literal values, variables, constants, operators etc that evaluates to a value.
For example, these are all examples of expressions:
1 * 2 * 3 (x + y) / 100 “Hello” + “World” If (true) “welcome” else “bye” 42
The thing about the code snippets above is that they can all evaluate to a value.
They are also single line expressions, but not all expressions need be on a single line. You can have expressions that span multiple lines. When such is the case, the multiple lines expressions get wrapped in curly braces.
For example:
{ val a = 40 val b = 2 a + b }
When you have multiple line expressions like this in Scala, the value it evaluates to is the last line of the expression. So in the case above, that would be 42.
Expressions are nothing special in programming languages, so how does this knowledge of expressions, especially multi-line expressions expressed with curly braces help in deciphering the particular Scala syntax I have long found confusing?
It is to see the code between the curly braces: {} as the expressions that they are, and then see whatever comes before the {} as an annotation that influences how the expression in the curly braces is evaluated, or how the result of the expressions is manipulated!
That is it! That simple.
Or to be more formal, instead of thinking of annotating, think of applying. So then you have:
It is to see the code between the curly braces: {} as the expressions that they are, and then see whatever comes before the {} as a function application that influences how the expression in the curly braces is evaluated, or how the result of the expressions is manipulated!
And if you think about it, there is really nothing controversial about this. Scala is a language that supports expressions quite well. In fact, control structures like if are expressions in Scala and they yield results. If you have a function call, the value you pass to it is basically an expression. What this approach does is to make the reader of the code aware of expressions, especially when you have multi-line expressions used as values to a function. This demystifies or removes the confusion associated with the use of curly braces that is normally used at definition sites to define things like function or classes.
Let’s revisit the code snippets we saw earlier on:
foo() { val a = "Hello" val b = "world" s"$a $b" }
This, as you will see later on, is a function call of a function defined with 2 multiple parameter lists. When called the second parameter is passed as a multi-line expression. The use of {} when a function is called, instead of () that is traditionally associated with function invocation does lead to some slight confusion.
So instead of trying to interpret this as your direct function call, would it help to apply the proposed suggestions of thinking first of the multi-line expression and whatever comes before it as a modifier?
Ok, let us put this view into practice.
Looking at the above, the multi-line expression encompassed with curly braces is the main code to focus on. So we have:
{ val a = "Hello" val b = "world" s"$a $b" }
and foo() is annotating it (or the function applied to it) . The question then is what is the effect of the foo()? To answer that we need to look into the definition of foo(). Let us imagine that foo()was defined as above:
def foo()(code: => String) = { println(s"Prefixing with foo..$code") }
Note: For those curious or found the definition above confusing, that definition is that way because Scala supports defining functions with multiple parameter lists. and also the ability for a function to have by-name parameters. I would touch more on this in a follow-up post, but in case you want to learn about this, then "defining functions with multiple parameter lists in Scala." and "by-name parameters in Scala" are the phrases you want to search for.
So what is going on in that code? It looks like foo is just prefixing with some text and then printing to console.
Thus when the code is executed, we should see “Prefixing with foo...Hello world” getting printed to the console.
That is it. The code in the curly braces is getting evaluated to "Hello world", while the effect of the foo() annotation/application is to take the "Hello world" prefix with "Printing with foo.." and then print to console.
What about this?
foo() { (x:Int) => val y = 2 println(s"output is: ${x * y}") }
The main part is the thing between the curly braces. In this case, it looks like a function. A function which gets passed an Int, and within the expression the Int gets doubled and printed to the console.
In this case, what is the foo() doing?
Well, It is also annotating the code in the curly braces, but this kind of annotation, unlike the previous one, seems to be injecting an Int value into the braces. So what really happens when the code is executed? To answer that, we need to check the definition of foo(). Which we can imagine such definition being:
def foo()(code: Int => Unit) = { println(“logging stuff”) code(3) }
Note: For those curious or found the definition above confusing, that definition is that way because Scala supports defining functions with multiple parameter lists. and also the ability to pass functions as a value to other functions. I would touch more on this in a follow-up post, but in case you want to learn about this, then "defining functions with multiple parameter lists in Scala." and "higher order function in Scala" are the phrases you want to search for.
The defined foo() seems to be logging some stuff to the console and passing 3 over.
So when we run the code we should get two log messages:
logging stuff // from the foo() annotation output is: 6 // from the expression
What about the piece of play framework code?
If you not familiar with play framework, the code below is just an example of how a controller method can be defined within the framework. If you are curious about the details, check the Play framework documentation
Action.async(action.parser) { request => Logger.info("Calling action") action(request) }
So what is going on here?
This time around I won't show the definition of Action.async(action.parser) which is playing the role of an annotation or function application. But without that, we should still be able to derive some meaning out of the code.
So let us try doing that.
The piece within the curly braces:
{ request => Logger.info("Calling action") action(request) }
is an expression which takes in a request, and then does stuff with the request value, and then yields as its value whatever action(request)returns. It is okay to consider this as the core of the code to parse when reading it.
The Action.async(action.parser), on the other hand, can then be seen as the annotation that ensures the request value is passed to the code in the curly braces, while at the same time taking whatever value it yields and use it in the request-response cycle of the play framework, so that a result can be returned back to the client calling the endpoint.
That is an explanation I can easily reason about.
Let's move on to the next code snippet.
So we have:
extractActiveMembershipFromRequest(membershipKey) { requesterId => ...some code here... }
The curly braces:
{ requesterId => ...some code here... }
is the main code. extractActiveMembershipFromRequest(membershipKey) is the annotation or application that passes requesterId into the expression.
Reading the name: extractActiveMembershipFromRequest we can safely conclude that in this case, the annotation/application ensures that the active membership is extracted from the request and passed on to the expression. It can be doing the extraction from the cookie, or session or some other place (to know for sure, we need to take a peep into its definition) but even without doing that we can still safely guess what it does and how it relates to the requesterId.
The same heuristic can be used to decipher the last code snippet
secure.hasRequiredRole(user, role) { submitRequest(user) }
with
{ submitRequest(user) }
being the expression evaluated. In this case, it would evaluate to whatever value submitRequest(user) evaluates to. It is then annotated with secure.hasRequiredRole(user, role) which, from the naming, looks like it is a function application performing authorization. Which means in this case, the expression within the curly braces would only evaluate if the secure.hasRequiredRole(user, role) application authorization passes.
You can actually apply this heuristic also to some of the inbuilt control structures in the language and it would still make sense.
For example, the while expression
val res = while(n < 10) { println(n) n = n + 1 }
Can be interpreted as containing this block
{ println(n) n = n + 1 }
Which prints stuff to the log, then increment the n variable.
The while(n < 10) can be seen as the annotation or function application for this block. And the effect of this is to repeatedly execute the block until the condition (n < 10) no longer holds
You can apply this also to if control expression
val n = 42 val result = if (n == 42) { "That's the answer to everything" } println(result)
where:
{ "That's the answer to everything" }
is the expression that evaluates to the "That's the answer to everything" string. The if (n == 42) is the annotation or function application that ensures that this only happens when n == 42.
It goes without saying though, that you can overdo this. And that there is a limit to how far this way of interpreting code in Scala can be stretched. But, what I have found out though, is that it has helped a great deal in being able to mentally parse code that I read which appears in any of the aforementioned sets of Scala syntax, and easily make sense of what is going on. Mostly code with syntax that takes the following form:
foo { }
foo { x: => }
foo.bar() { }
etc.
But why did I find code taking the form above confusing in the first place? Even when I have always understood the features of Scala that made it possible to write code like this? By features of Scala, I mean multi-line expressions, functions with multiple parameter lists, functions with by-name parameter, and functions as values.
The answer to that has to do with some preconceived notion that I have developed about how {} should be interpreted based on experience with other c-family of languages I had worked with in time past.
I would expatiate more on this in a follow-up post, but in the meantime, I hope someone else finds the approach explained in this post useful.
Have you always read code using this heuristic? Do you have other forms of heuristics you use? Did you find this post useful? or you have some feedback on how it can be improved? Then do not hesitate to leave a comment!
3 comments:
Maybe I'm simply used to the syntax but I just see them as arguments.
A method defined as
def foo(a: Int)(b: Int => String)(c: String => Unit)
is called like foo(1){ x => (x + 4).toString }(println)
I think the annotation part you're talking of just make things more confusing.
But why would you even want to call that using the curly braces? The x => (x + 4).toString expression is one single line, so no need for that, thus it should be called like this:
foo(1)( x => (x + 4).toString )(println)
You use the {} braces when you have expressions that span multiple lines. In such situations, then I have found it useful to focuse on the code in the curly braces, ie the multiple line expression, while viewing whatever comes before as some form of annotations that modifies of influences the value the code in the braces expresses too.
Also, I mentioned this interpretation can be overstretched. So if you see a situation where it does not apply, then do not force it.
I have, personally found this very useful. If you find it confusing, then, by all means, do not use it.
very useful, thank you!
Post a Comment