What is the difference between the + operator and std.mergePatch in Jsonnet? - merge

Jsonnet's std.mergePatch implements RFC7396, but in my naive testing I didn't find a different between the way it behaved and the + operator; e.g. the + operator respects x+ syntax. std.mergePatch is implemented in Jsonnet itself, which seems to imply that it is different than the + operator, which I'm assuming is a builtin.
What is different about the semantics of these two ways of merging?

Jsonnet's + and std.mergePatch are completely different operations. The + operator operates only on a single level and std.mergePatch traverses the object recursively and merges the nested objects. It's easiest to explain with an example:
local foo = { a: {b1: {c1: 42}}},
bar = { a: {b2: {c2: 2}}};
foo + bar
Output:
{
"a": {
"b2": {
"c2": 2
}
}
}
Note that the bar.a completely replaced foo.a. With + all fields in the second object override the fields in the first object. Compare that with the result of using std.mergePatch(foo, bar).
{
"a": {
"b1": {
"c1": 42
},
"b2": {
"c2": 2
}
}
}
Since both foo and bar have a field a, is is merged and the final results contains both b1 and b2.
So to reiterate, + is a "flat" operation which overrides the fields of the first object with the fields of the second object.
This is not the end of the story, though. You mentioned field+: value syntax and I will try to explain what it really does. In Jsonnet + is not just overwriting, but inheritance in OO sense. It creates an object which is the result of the second object inheriting from the first one. It's a bit exotic to have an operator for that – in all mainstream languages such relationships are statically defined. In Jsonnet, when you do foo + bar, the bar object has access to stuff from foo through super:
{ a: 2 } + { a_plus_1: super.a + 1}
This results in:
{
"a": 2,
"a_plus_1": 3
}
You can use this feature to merge the fields deeper inside:
{ a: {b: {c1: 1}, d: 1}} +
{ a: super.a + {b: {c2: 2} } }
Resulting in:
{
"a": {
"b": {
"c2": 2
},
"d": 1
}
}
This is a bit repetitive, though (it would be annoying if the field name was longer). So we have a nice syntax sugar for that:
{ a: {b: {c1: 1} , d: 1}} +
{ a+: {b: {c2: 2}} }
Please note that in these examples we only did the merging for one particular field we chose. We still replaced the value of a.b. This is much more flexible, because in many cases you can't just naively merge all stuff inside (sometimes a nested object is "atomic" and should be replaced completely).
The version in +: works in the same way as the version with super. The subtle difference is that +: actually translates to something like if field in super then super.field + val else val, so it also returns the same value when super is not provided at all or doesn't have this particular field. For example {a +: {b: 42}} evaluates just fine to {a: { b: 42 }}.
Mandatory sermon: while + is very powerful, please don't abuse it. Consider using using functions instead of inheritance when you need to parameterize something.

Related

Recursively building a list of lists

The following code compiles as long as the return type of the recursive call is Any, but obviously I'm doing something wrong because it should not have to be Any.
case class Group(
id: Long = -1,
parentId: Long = -1,
name: String = "")
def makeTree(groupId: Long, groups: List[Group]) = {
def getAllChildren(gid: Long): Any = {
def children = for {
g <- groups; if g.parentId == gid
} yield g
if (children.isEmpty) List()
else {
children map { x =>
getAllChildren(x.id)
}
}
}
getAllChildren(groupId)
}
val groups = List(Group(1, 0, "A"),
Group(2, 1, "B"),
Group(3, 1, "C"),
Group(4, 2, "D"))
makeTree(1, groups)
//Results in: Any = List(List(List()), List())
}
If I change the signature of getAllChildren to:
def getAllChildren(gid: Long): List[Group]
then I get an error:
type mismatch; found : List[List[Group]] required: List[Group]
What am I doing wrong here.
Not really speaking scala, but it looks like, for some id, you collect the groups with that id, and then replace each group with a list of it's children, and so on.
Specifically, here:
if (children.isEmpty) List()
else {
children map { x =>
getAllChildren(x.id)
}
}
Indeed, here is the root of your error: Your algorithm allows for infinite recursion, and each recursion adds another List[...] around your return type. But you can't have a type with dynamic depth.
For example, if you try to fix this by giving type List[List[Group]], it will complain that it found List[List[List[Group]]], and so on, until you give up.
This is the way typecheckers tell us that we're about to program a potentially infinite recursion. You may have assumed the invariant that your hierarchy can't involve loops, yet this is not reflected in the types. In fact, it is not hard to construct an example where two groups are each others parents. In that case, your program will produce an ever deeper nesting list until it terminates due to lack of memory.
Note that you can't fix your code simply by using flatMap instead of map: the reason being that getAllChildren never ever produces a list with Group elements. It either returns an empty list, or, a flattened list of empty lists, that is, an empty list. Hence, if it should return at all, it would return an empty list in the flatmap version.
You need to change the call to children map ... for children flatMap ..., otherwise you're not returning a List[Group] but potentially a List[List[.....]] like #Ingo suggests. flatMap will map each element to a List and then flatten all lists to create just one List[Group] containing all children.
EDIT: As #Ingo points out, even if flatMap would solve the typing problem, your function still doesn't work, since you always return an empty List. You should go for
children flatMap { x => x :: getAllChildren(x.id, acc) }
to prepend the child to the child's children.
Your code will work if you flatten the list returned on children map like this:
def makeTree(groupId: Long, groups: List[Group]) = {
def getAllChildren(gid: Long): List[Group] = {
def children = for {
g <- groups; if g.parentId == gid
} yield g
if (children.isEmpty) List()
else {
val listList = children map { x =>
x :: getAllChildren(x.id)
}
listList.flatten
}
}
getAllChildren(groupId)
}
The reason this is happening is that you are taking a List[Group] and mapping it over a function that also returns a List[Group], thus resulting in a List[List[Group]]. Simply flattening that List will produce the desired result.

The most idiomatic way to perform conditional concatenation of strings in Scala

I'm curious what would be the best way to build a String value via sequential appending of text chunks, if some of chunks dynamically depend on external conditions. The solution should be idiomatic for Scala without much speed and memory penalties.
For instance, how one could re-write the following Java method in Scala?
public String test(boolean b) {
StringBuilder s = new StringBuilder();
s.append("a").append(1);
if (b) {
s.append("b").append(2);
}
s.append("c").append(3);
return s.toString();
}
Since Scala is both functional and imperative, the term idiomatic depends on which paradigm you prefer to follow. You've solved the problem following the imperative paradigm. Here's one of the ways you could do it functionally:
def test( b : Boolean ) =
"a1" +
( if( b ) "b2" else "" ) +
"c3"
These days, idiomatic means string interpolation.
s"a1${if(b) "b2" else ""}c3"
You can even nest the string interpolation:
s"a1${if(b) s"$someMethod" else ""}"
What about making the different components of the string functions in their own right? They have to make a decision, which is responsibility enough for a function in my book.
def test(flag: Boolean) = {
def a = "a1"
def b = if (flag) "b2" else ""
def c = "c3"
a + b + c
}
The added advantage of this is it clearly breaks apart the different components of the final string, while giving an overview of how they fit together at a high level, unencumbered by anything else, at the end.
As #om-nom-nom said, yours is already sufficiently idiomatic code
def test(b: Boolean): String = {
val sb = new StringBuilder
sb.append("a").append(1)
if (b) sb.append("b").append(2)
sb.append("c").append(3)
sb.toString
}
I can suggest an alternative version, but it's not necessarily more performant or "scala-ish"
def test2(b: Boolean): String = "%s%d%s%s%s%d".format(
"a",
1,
if (b) "b" else "",
if (b) 2 else "",
"c",
3)
In scala, a String can be treated as a sequence of characters. Thus, an idiomatic functional way to solve your problem would be with map:
"abc".map( c => c match {
case 'a' => "a1"
case 'b' => if(b) "b2" else ""
case 'c' => "c3"
case _ =>
}).mkString("")

Why scala's pattern maching does not work in for loops for type matching?

I'm coding against an API that gives me access to remote file system. The API returns a list of files and directories as list of node objects (parent to file and directory).
I want to work only on a directories, ignoring files. I've tried to use type pattern matching in for loop but it does not work:
for {
dir: CSDir <- workarea.getChildren() // <-- I'm getting an error here complaining about type conversion
} {
println(dir)
}
Here is a similar example using scala basic objects to run it without dependencies:
val listOfBaseObjects:List[Any] = List[Any]("a string", 1:Integer);
for (x: String <- listOfObjects) {
println(x)
}
I end up using a regular pattern matching in side of for loop and that works fine:
// This works fien
for (child <- workarea.getChildren()) {
child match {
case dir: CSDir => println(dir)
case _ => println("do not nothing")
}
}
Question:
Can you tell me why the first /second example does not work in scala 1.9?
In the "Programming in Scala" the for loop is advertised to use the same pattern matching as the match so it should work.
If the for and match are different it would be great if you could point me to some articles with more details. What about pattern matching in assignment?
Update:
I can't accept an answer that states that it is impossible to skip elements in for loop as this contradicts with the "Prog. in scala". Here is a fragment from section 23.1:
pat <- expr ... The pattern pat gets matched one-by-one against all elements of that list. ... if the match fails, no MatchError is thrown. Instead, the element is simply discarded from the iteration
and indeed the following example works just fine:
scala> val list = List( (1,2), 1, 3, (3,4))
scala> for ((x,y) <- list) { println (x +","+ y) }
1,2
3,4
Why then type matching does not work?
This is the long-standing issue 900 and has been discussed many times before. The common workaround is to use something like:
for (y#(_y:String) <- listOfBaseObjects) {
println(y)
}
A nicer version is provided by Jason Zaugg in the comments to the above-mentioned ticket:
object Typed { def unapply[A](a: A) = Some(a) }
for (Typed(y : String) <- listOfBaseObjects) {
println(y)
}
What you want to do is essentially: iterate over all elements of workarea.getChildren() that are of type CSDir (in other words: matching some criteria). Ordinary loop/for comprehension iterates over all elements. You cannot say: iterate over all elements having this type and skip others. You must be more explicit.
What do you think about:
workarea.getChildren() collect {case dir: CSDir => dir} foreach println
It does exactly what you want: collect all elements of workarea.getChildren() and for each of them call println.
How about this:
val listOfBaseObjects: List[Any] = List[Any]("a string", 1:Integer);
for (x <- listOfBaseObjects if x.isInstanceOf[String]) {
println(x)
}

Strange behavior in Scala Parallel View

According to Scaladoc,
A view is a lazy version of some collection. Collection transformers such as map or filter or ++ do not traverse any elements when applied on a view. Instead they create a new view which simply records that fact that the operation needs to be applied.
That means the operations won't be applied until the elements is accessed. But how about Parallel ?
Take a look at this example:
def tn = Thread.currentThread.getName
val strList = List("I", "am", "a" , "student", ".", "I", "come", "from", "China", ".","I","love","peace")
val pvs = strList.par.view.filter{ s => println("f "+ tn); s == "I"}.map{s => println("m " + tn); s.toLowerCase}
The second will print out like the following :
When you apply foreach on pvs , it outputs :
I can't understand why the performance of Parallel style is not the same as the normal one :
val strList = List("I", "am", "a" , "student", ".", "I", "come", "from", "China", ".","I","love","peace") // or read from a text file , e.g. article.txt
strList.view.filter{s => println("f"); s == "I"}.map{s => println("m"); s.toLowerCase}.foreach(s => println("p"))
Because the interpreter evaluates the expression by forcing it if the expression is a parallel collection view, so that it could print it (in essence, forces the view). Try either running this as a standalone Scala program, or do this:
scala> object foo { var bar: AnyRef = null }
scala> foo.bar = strList.par.view.filter{ s => println("f "+ tn); s == "I"}.map{s => println("m " + tn); s.toLowerCase}
EDIT:
Another issue above is the filter method on parallel views - unlike regular views, it is implemented by forcing the collection. This means that the moment you call filter on the parallel view, the entire filtered collection will be forced into an array and the predicate associated with the filter will have to be called. Methods like groupBy do the same thing on regular views.

Output of Iterable.sliding as Tuple

The method sliding on collections returns a sliding window of given size in the form of X[Iterable[A]] with X being the type of the collection and A the element type. Often I need two or three elements and I prefer to have them named. One ugly workaround for sliding(2) is the following:
points.sliding(2).foreach{ twoPoints =>
val (p1,p2) = (twoPoints.head,twoPoints.last)
//do something
}
This sucks and only works for two elements. Also note that
(a,b) = (twoPoints(0),twoPoints(1))
doesn't work.
I did a lot of that in this answer just last week.
points.sliding(2).foreach { case X(p1, p2) => ... }
If points is an Array, then replace X with Array. If it is a List, replace X with List, and so on.
Note that you are doing a pattern match, so you need to {} instead of () for the parameter.
twoPoints would appear to be a List. Try this:
points.sliding(3).foreach{ _ match {
case Seq(a, b, c) => {
//do something
}
}
You'll be surprised what sorts of kung fo pattern matching lets you get away with.
I recently wanted a little more sugar in my sliding iterators, so I came up with this:
implicit class SlidingOps[A](s: Seq[A]) {
def slidingPairs = (s, s.tail).zipped
def slidingTriples = (s, s.tail, s.tail.tail).zipped
}
This works with any Seq, but is probably most efficient with List. .zipped returns a scala.runtime.Tuple2Zipped (or Tuple3Zipped for a 3-element tuple) object, which defines several familiar higher-order methods so that their arguments take multiple arguments, so you can write:
points.slidingPairs.foreach { (a, b) => ... }
or even:
(1 to 10).slidingTriples.map(_ + _ + _)
You can optimize the implementation further if you want it to be really efficient for non-list types.