2013-06-24 17 views
8

Wystarczy użyć podstawowego interfejsu JDBC do odczytu niektórych danych przy użyciu Scala. W F # (przy użyciu przestrzeni nazw System.Data.SqlClient) moglibyśmy zrobić coś takiego, aby zwrócić niezmienną listę z bazy danych.Scala elegancka lista rozumienia jak w F #

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

W Scali jest to trudniejsze, o ile wiem, nie ma zrozumienia "podczas", takiego jak F #. W rzeczywistości chciałbym zrobić coś bliskiego F # w Scali bez używania zmiennych zmiennych - choćby dlatego, że wyglądają paskudnie i dodają do Linii kodu.

Coś jak to wydaje się być powszechne w moim kodu Scala teraz:

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

Właściwie w idiomatycznej scali wyglądałoby to tak: 'val rs = stmt.executeQuery(); val result = for (r <- rs) yield r.getInt (1) '(tylko pseudokod, jeśli zechcesz) –

+0

Możesz rzucić okiem na ten post: http://stackoverflow.com/questions/2102662/scala-ekspozycja-a-jdbc-resultset-through-a-generator-iterable/15950556 # 15950556 –

+2

jeśli szukasz zapytań podobnych do LINQ w scala, spójrz na [Slick] (http: // slick. typesafe.com/) –

Odpowiedz

9

Chciałbym utworzyć niestandardową podklasę Iterator która otacza wynik zapytania. To naprawdę proste; senia pokazała jak.

Ale można też

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

Można używać w taki sam sposób w Scala, ale myślę, że jest brzydka:

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) 

Idiomatic scala sposobem jest do konwertowania Reader do 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) 

Ale jeśli jesteś naprawdę brakuje tej składni można ją dodać:

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

Świetna odpowiedź. Wznowiono także wszystkie inne odpowiedzi, które są doskonałe. –

5

można użyć niejawny klasę wraz z domniemanym CanBuildFrom. To nie używać zmienny budowniczy, ale nie po stronie rozmówcy:

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

być używane tak:

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

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

println(names) 
rs.close() 

dla zrozumienia używa map pod maską, więc jeśli wolisz map bezpośrednio:

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

, który tworzy sekwencję. Dzięki CanBuildFrom można produkować inne kolekcje, jak również poprzez dostarczenie typ wyraźnie:

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

Jak CanBuildFrom pracę? Kompilator Scala analizuje typy związane z tym wyrażeniem: Istnieje wynikowy typ i typ zwracany przez funkcję wywoływaną przez mapę. Na podstawie tych informacji kompilator Scala dostarcza domyślnie fabrykę, której można użyć do utworzenia odpowiedniego konstruktora. Potrzebujesz więc tylko jednej metody do produkcji różnych rodzajów kolekcji.

Jeśli chcesz zwracać wiele wartości, po prostu wrócić krotki:

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

i krotka mogą być wykorzystane do stworzenia Map bezpośrednio:

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

ta opiera się na założeniu, że zestaw wyników jest listą wierszy, więc funkcja map powinna być dostępna na wierzchu. Niejawna klasa służy do niejawnego dodania metody map do podstawowego zestawu wyników.

+0

Warto wyjaśnić magię o 'CanBuildFrom' ;-) –

+0

@ om-nom-nom Ale to zniszczyłoby magię :-) Dodałem wyjaśnienie. – Beryllium