2013-06-24 4 views
8

Просто используя базовый интерфейс JDBC для чтения некоторых данных с помощью Scala. В F # (используя пространство имен System.Data.SqlClient) мы могли бы сделать что-то вроде этого, чтобы вернуть неизменный список из базы данных.Scala элегантный список, как в F #

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

В Scala это оказывается более трудным, насколько я знаю, нет «а» понимание как F #. Эффективно я хотел бы сделать что-то близкое к F # в Scala без необходимости использовать изменчивые vars - хотя бы потому, что они выглядят уродливыми и добавляют к Lines of Code.

Нечто подобное, кажется, прямо сейчас, чтобы быть обычным явлением в моем коде Scala:

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

Фактически в идиоматической scala это будет выглядеть как: val rs = stmt.executeQuery(); val result = for (r <- rs) yield r.getInt (1) '(просто псевдокод, если хотите) –

+0

Вы можете посмотреть это сообщение: http://stackoverflow.com/questions/2102662/scala-exposing-a-jdbc-resultset-a-generator-iterable/15950556 # 15950556 –

+2

Если вы ищете LINQ-подобные запросы в scala, посмотрите [Slick] (http: // slick. typesafe.com/) –

ответ

9

Я хотел бы создать пользовательский подкласс Iterator, который оборачивает результат запроса. Это очень легко; Сения показала, как.

Но вы также можете

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

Вы можете использовать так же, как в Скале, но я думаю, что это некрасиво:

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) 

Идиоматические Скала путь конвертировать 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) 
+0

Отличный ответ. Здесь также поддерживаются все другие ответы, которые превосходны. –

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

для понимания использует 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 в базовый набор результатов неявно.

+0

Стоит объяснить магию о 'CanBuildFrom' ;-) –

+0

@ om-nom-nom Но это разрушит магию :-) Я добавил объяснение. – Beryllium

Смежные вопросы