views:

101

answers:

2

I'm trying to integrate a Lift application into some existing Java code. In one of my snippets, I have an Array of Java objects that I need to map that into a NodeSeq. I can get an Array of Node's, but not a NodeSeq. (At least, not in very functional-looking way).

import scala.xml.NodeSeq

// pretend this is code I can't do anything about
val data = Array("one", "two", "three")

// this is the function I need to write
def foo: NodeSeq = data.map { s => <x>{s}</x> }
//                          ^
// error: type mismatch;
//  found   : Array[scala.xml.Elem]
//  required: scala.xml.NodeSeq

What's the cleanest way to do this?

+6  A: 
scala> import collection.breakOut
import collection.breakOut

scala> def foo: NodeSeq = data.map { s => <x>{s}</x> }(breakOut)
foo: scala.xml.NodeSeq

The method map actually has two argument lists. The first accepts a function, which you passed. The second accepts a CanBuildFrom object which is used to create a builder that then builds the returning sequence. This argument is implicit, so usually the compiler fills it for you. It accepts 3 type parameters: From, T, To. There are several predef implicits (including in object NodeSeq), but none of them matches From=Array, T=Node, To=NodeSeq.

breakOut solves this: it is a generic method that returns a CanBuildFrom instance by searching for an implicit CanBuildFrom[Nothing, T, To]. According to the implicit search rules, any CanBuildFrom that matches T, To and has From > Nothing is acceptable. In this case: canBuildFrom in object Array

IttayD
It seemed appropriate to link to the breakOut question: http://stackoverflow.com/questions/1715681/scala-2-8-breakout
eed3si9n
+4  A: 

I would simply convert map output to sequence (given that Seq[Node] is a super-class of NodeSeq)

scala> def foo: NodeSeq = data.map { s => <x>{s}</x> } toSeq
foo: scala.xml.NodeSeq

or use foldLeft instead of map

scala> def foo: NodeSeq = (Seq[Node]() /: data) {(seq, node)=> seq ++ <x>{node}</x>}
foo: scala.xml.NodeSeq
Vasil Remeniuk
toSeq is the elegance I'm looking for. Thanks!
dave
Using map and then toSeq means you're creating 2 collections. Using breakOut you're creating 1.
IttayD
`toSeq` creates a wrapper(http://www.scala-lang.org/api/current/scala/collection/mutable/WrappedArray.html), so there's a miserable performance penalty. http://www.scala-lang.org/docu/files/collections-api/collections_38.html
Vasil Remeniuk