scalapass-by-name

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~


Solution

  • 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.)