Scala优雅列表理解如F#

时间:2013-06-24 13:26:22

标签: scala jdbc list-comprehension

使用基本的JDBC接口使用Scala读取一些数据。 在F#中(使用System.Data.SqlClient命名空间),我们可以这样做,从数据库中返回一个不可变列表。

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

在Scala中,这被证明更加困难,据我所知,没有像F#这样的“同时”理解。实际上,我想在Scala中做一些接近F#的事情而不必使用可变变量 - 如果只是因为它们看起来很难看并添加到代码行中。

这样的东西现在似乎在我的Scala代码中很常见:

 var result = Seq.empty[Int]
 val rs = stmt.executeQuery()
 while (rs.next()) {
     result = result :+ rs.getInt(1) }

3 个答案:

答案 0 :(得分:9)

我会创建一个包含查询结果的Iterator自定义子类。这真的很容易; senia展示了如何。

但你也可以

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

答案 1 :(得分:7)

你可以在scala中使用相同的方式,但我认为这很难看:

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)

惯用scala方式是将您的Reader转换为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)

但如果您真的错过了这种语法,可以添加它:

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)

答案 2 :(得分:5)

您可以将隐式类与隐式CanBuildFrom一起使用。这确实使用了一个可变构建器,但不是在调用者端:

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()
    }
  }
}

这样使用:

import MyResultSetContainer._
val rs = stmnt.executeQuery("select * from pg_user")

val names = for (row <- rs) yield (row.getString(1))

println(names)
rs.close()

for comprehension使用了map,所以如果您直接选择map

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

产生序列。感谢CanBuildFrom您还可以通过明确提供类型来生成其他集合:

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

CanBuildFrom如何运作? Scala编译器会查看此表达式中涉及的类型:结果类型和map调用的函数返回的类型。根据此信息,Scala编译器隐式提供工厂,可用于创建合适的构建器。因此,您只需要一种方法来生成不同类型的集合。

如果要返回多个值,只需返回一个元组:

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

并且元组可用于直接创建Map

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

这是基于结果集是行列表的想法,因此map函数应该可用于它。隐式类用于隐式地将map方法添加到基础结果集。