Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set a do-nothing handler to a by-name parameter?

I defined a method treeNode to create a node, and which can have children nodes. The simplified code is:

def treeNode(text:String) (children: => Any) {
    val b = new TreeNode(text)
    children
}

When I use this method, I have to write:

treeNode("aaa") {
    treeNode("bbb") {}
    treeNode("ccc") {}
}

You can see the leaf nodes they don't have children, but they have to have a empty block {}.

Is there any way to give the parameter children: => Any a default do-nothing value, that I can write the code as:

treeNode("aaa") {
    treeNode("bbb")
    treeNode("ccc")
}

Help~

like image 278
Freewind Avatar asked Dec 07 '22 17:12

Freewind


2 Answers

The problem is not that you can't give it a do-nothing (default) value; the problem is that even if you do, functions with multiple parameter blocks have to at least have parentheses or braces for each block.

There is one exception: an implicit parameter block does not need to be referenced at all. Unfortunately, you're not allowed to have call-by-name implicit parameters, and even if you were, your signature would allow any random implicit to work in that spot!

Now, there is a way around this, which I will show for completeness, but I suggest that (assuming you don't just want another name, like leafNode) you just leave the trailing {} there.

You can get exactly the syntax that you want if you do the following. First, you need an implicit parameter, but you make it a wrapper class (could use Function0 which already exists, but then the next step might have unintended consequences):

trait AnyByName { def eval: Any }
def treeNode(text: String)(implicit children: AnyByName) = (text,children.eval)

Now you need two things--you need to be able to convert a by-name Any into your new trait, and you need to have an implicit do-nothing one available. So we

implicit val nameForDoingNothing = new AnyByName { def eval = () }
implicit def wrap_any_by_name(a: => Any) = new AnyByName { def eval = a }

And now we recover the behavior that you were after:

scala> treeNode("Hi")
res1: (String, Any) = (Hi,())

scala> treeNode("Hi") { treeNode("there") }
res2: (String, Any) = (Hi,(there,()))

(in your example, you don't return anything; here I do, to show that it works.)

It's a lot of tooling just to avoid some {}s, though, which is why I'd suggest only doing this if you anticipate this to be a very heavily used DSL and that two names is unacceptable. (Also, if you expect it to be very heavily used, treeNode is probably painfully long as a name; I'd suggest just node.)

like image 61
Rex Kerr Avatar answered Dec 25 '22 06:12

Rex Kerr


AFAICT you won't be able to pass a kind-of empty block by default.

Btw, you could really simply partition the task based on your treeNode function by creating another one def leaf(text:String) = treeNode(t) {}

like image 25
Andy Petrella Avatar answered Dec 25 '22 08:12

Andy Petrella