Scala elegant list comprehension, like in F #

Just using the basic JDBC interface to read some data using Scala. In F # (using the System.Data.SqlClient namespace), we could do something like this to return an immutable list from the database.

let rs = cmd.ExecuteReader() [while rs.Read() do yield rs.GetInt32(1)] 

In Scala, this turns out to be more complicated, as far as I know, there is no notion of β€œyet” like F #. In fact, I would like to do something close to F # in Scala without having to use mutable vars - if only because they look ugly and are added to Lines of Code.

Something like this seems normal to me in my Scala code right now:

  var result = Seq.empty[Int] val rs = stmt.executeQuery() while (rs.next()) { result = result :+ rs.getInt(1) } 
+8
scala jdbc list-comprehension
source share
3 answers

I would create a custom subclass of Iterator that completes the query result. It is very easy; Senia showed how.

But you can also

 val rs = stmt.executeQuery val it = Iterator.continually(if (rs.next()) Some(rs.getInt(1)) else None) val result = it.takeWhile(_.isDefined).toList.flatten 
+9
source share

You can use the same method in scala, but I think this is ugly:

 class Reader(var i: Int){ def read = { i-=1; i > 0 } def getInt32 = i } val r = new Reader(10) Stream.from(0).takeWhile{ _ => r.read}.map{ _ => r.getInt32}.toList // List(9, 8, 7, 6, 5, 4, 3, 2, 1) 

The idiomatic scala way is to convert your Reader to Iterator :

 implicit class ReaderIterator(r: Reader) extends Iterator[Int] { def hasNext = r.read def next = r.getInt32 } scala> new Reader(10).toList res0: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1) 

But if you really lack this syntax, you can add it:

 import scala.collection.immutable.VectorBuilder class FWhile(c: => Boolean){ def apply[T](e: => T): Seq[T] = { val b = new VectorBuilder[T] while (c) b += e b.result } } object FWhile{ def apply(c: => Boolean) = new FWhile(c) } scala> FWhile(r.read){r.getInt32} res0: Seq[Int] = Vector(9, 8, 7, 6, 5, 4, 3, 2, 1) 
+7
source share

You can use the implicit class with the implicit CanBuildFrom . This uses a volatile builder, but not on the caller side:

 object MyResultSetContainer { implicit class MyResultSet(rs: ResultSet) { def map[T, C <: Iterable[T]](f: (ResultSet) => T) (implicit cbf: CanBuildFrom[Nothing, T, C]): C = { val builder = cbf() while (rs.next()) { builder += f(rs) } builder.result() } } } 

for use as follows:

 import MyResultSetContainer._ val rs = stmnt.executeQuery("select * from pg_user") val names = for (row <- rs) yield (row.getString(1)) println(names) rs.close() 

For understanding, map used under the hood, so if you prefer map directly:

 val names = rs.map(row => row.getString(1)) 

which creates a sequence. Thanks to CanBuildFrom you can create other collections by explicitly providing a type:

 val names: List[String] = rs.map(row => row.getString(1)) 

How does CanBuildFrom work? The Scala compiler looks at the types involved in this expression: there is a resulting type and a type returned by a function called map. Based on this information, the Scala compiler provides the factory implicitly, which can be used to create a suitable builder. Therefore, you only need one method to create different types of collections.

If you want to return multiple values, just return the tuple:

 val columns = rs.map(row => (row.getInt(2), row.getString(1))) 

and the tuple can be used to directly create a map :

 val keyNamesMap: Map[Int, String] = rs.map(row => (row.getInt(2), row.getString(1))) 

This is based on the idea that the result set is a list of strings, so the map function should be available on top of it. An implicit class is used to add the map method to the base result set implicitly.

+5
source share

All Articles