4
\$\begingroup\$

Consider the following piece of code:

var result = List[Measurement]()
val dbResult = conn.createStatement.executeQuery(unsentMeasurements)
while(dbResult.next) {
 result ::= Measurement(dbResult.getString("stuff"), dbResult.getString("morestuff"))
}
result.reverse

Versus this:

val dbResult = conn.createStatement.executeQuery(unsentMeasurements)
Stream.continually(dbResult).takeWhile(_.next) map { r =>
 Measurement(r.getString("stuff"), r.getString("morestuff"))
}

Any flaws in my rationale? Any improvements?

asked Dec 10, 2011 at 13:26
\$\endgroup\$
1
  • \$\begingroup\$ The second example looks better to me. I can't try it out right now, but is there a possibility to use Iterator instead of Stream? \$\endgroup\$ Commented Dec 16, 2011 at 8:56

1 Answer 1

3
\$\begingroup\$

Of course this is not purely functional, although it represents an improvement. What would a purely functional solution look like?

trait Sql[A] { self =>
 def unsafePerformIO(ds: javax.sql.DataSource): A = unsafePerformIO(ds.createConnection)
 def unsafePerformIO(conn: java.sql.Connection): A //abstract
 def map(f: A => B): Sql[B] = new Sql[B] { 
 def unsafePerformIO(conn: Connection) = f(self.unsafePerformIO(conn))
 }
 def flatMap(f: A => Sql[B]): Sql[B] = new Sql[B] { 
 def unsafePerformIO(conn: Connection)
 = f(self.unsafePerformIO(conn)).unsafePerformIO(conn)
 }
}

I imagine you might say "huh?" at this point. Let me introduce the companion:

object Sql {
 def apply[A](comp: Connection => A) = new Sql[A] {
 def unsafePerformIO(conn: Connection) = comp(conn)
 }
}

The idea (of course) is that you delay side-effects until the end of the world, encapsulating the values returned by the database in a type that represents value returned from DB call, in the same way Option represents value which may not exist.

The usage becomes something like this:

Sql { conn => process( conn.prepareStatement.executeQuery ) }

Where the process method might look like your code (above) perhaps.

I imagine this may raise a few questions:

What the? Huh? I mean, what the?

Well, it allows you to do something like this:

def getStuff1: Sql[Int]
def getStuff2: Sql[Int]
def useStuff(i: Int): Sql[String]

Then you can compose using for-comprehensions

val result = 
 for {
 i <- getStuff1
 j <- getStuff2
 s <- useStuff(i + j)
 } 
 yield s

What is the type of this expression? Well, it's an Sql[String] of course! To get hold of the String:

result.unsafePerformIO(myDataSource)

This creates a single connection which is then threaded through the computation.

What is missing? Well, resource management (i.e. cleaning up the connections of course). I understand this will all be available in scalaz7

answered Feb 21, 2012 at 20:01
\$\endgroup\$
1
  • \$\begingroup\$ So you ah.. hmm.. made an Sql Monad? :) \$\endgroup\$ Commented Feb 28, 2012 at 15:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.