Mike Slinn

Using Partially Applied Functions

— Draft —

Published 2014-08-09. Last modified 2015-09-17.
Time to read: 11 minutes.

Common usage patterns of partially applied functions are introduced, including the Loan Pattern, various ways of using type hints, nested partially applied functions and generating partially applied functions from parametric methods. An extended review of partially applied functions will stretch your brain. The exercise walks you through using partially applied functions to make writing database code easy.

The sample code for this lecture can be found in courseNotes/src/main/scala/PartiallyAppliedUsing.scala.

We need a few methods and objects for this lecture, which are defined in the Partially­Applied­Stuff object in the sample code for this course. The first2lines helper method returns the first two lines of the given InputStream as a String.

Scala code
def first2lines(msg: String, inputStream: InputStream): String =
  s"$msg:\n  " + read(inputStream).split("\n").take(2).mkString("\n  ")

first2lines calls read, which uses Iterator.continually. We learned about Interator.continually in the Immutable Collections lecture of this course. Given an InputStream, read returns the contents of the stream as a String.

Scala code
def read(inputStream: InputStream): String =
  Iterator.continually(inputStream.read)
          .takeWhile(_ != -1)
          .map(_.toChar)
          .mkString

We’ll also need a File instance, which I’ve called file, and a supply of FileInputStream instances, which are provided by the fileInputStream method.

Scala code
val file = new File("/etc/passwd")

def fileInputStream = new FileInputStream(file)

The Loan Pattern

The Loan Pattern loans a resource to a higher-order function. Implementations of this pattern do the following:

  • Create or access a resource
  • Pass the resource to the the higher-order function
  • Dispose of the resource reliably, efficiently and safely

The advantages of the loan pattern are:

  • The loan pattern implementation is completely separate from the higher-order function
  • Various higher-order functions can be provided
  • Higher-order functions are not concerned about the creation or destruction of the resource because the loan pattern implementation handles that

Before we go any further, you might want to refresh your memory of how call-by-name / lazy evaluated parameters work by reviewing the More Fun With Functions lecture of the Introduction to Scala course.

The Loan Pattern is actually an extension of the withT method that we encountered in the Parametric Types lecture of this course so that it safely handles the opening and closing of a resource. withT is parametric in T and U. T is the type of the object that withT will operate on, and the operation transforms the instance of T into an instance of U. The body of withT merely consists of invoking the operation and passing it the value of the t parameter.

Scala code
def withT[T, U](t: T)(operation: T => U): U = operation(t)

... and here is how we could use withT. We create a new java.util.Date, and pass it to the t parameter of the withT method. The operation parameter is bound to the block of code that simply consists of the println method, and that means that the body of withT invokes the println method and passes it the Date instance.

Scala code
withT(new java.util.Date) { println }

Here is the definition of an implementation of the Loan Pattern, called withCloseable:

Scala code
import java.io._
import scala.util.{Failure, Success, Try}
def withCloseable[C <: Closeable, T](factory: => C)(operation: C => T): Try[T] = { val closeable = factory try { val result: T = operation(closeable) closeable.close() Success(result) } catch { case throwable: Throwable => try { closeable.close() } catch { case _: Throwable => } Failure(throwable) } }

withCloseable is parametric in types C and T, where C is constrained to be a subtype of java.io.Closeable. withCloseable accepts two Function1 parameters and returns a Try. The first of withCloseable’s Function1 parameters is a lazily evaluated parameter called factory of type C. The result of evaluating factory is cached. That is important because as we learned in the More Fun With Functions lecture of the Introduction to Scala course, lazily evaluated parameters are evaluated each time they are referenced. The second Function1 is an operation to be performed on the Closeable instance. The Closeable is automatically closed after operation completes or if it throws an exception. The withCloseable method returns a Try, which wraps the result from running operation.

Let’s invoke withCloseable such that both parameters are bound with two arguments.

Scala code
withCloseable(fileInputStream) { fis =>
  first2lines("withCloseable example 1", fis)
}

The above causes the contents of /etc/passwd to be displayed. The first parameter list constructs a FileInputStream, and the second parameter list receives the FileInputStream then passes it to the first2lines method we just defined. As we learned in the Functions are First Class lecture of the Introduction to Scala course, you could also write this using shorthand like this:

Scala code
withCloseable(fileInputStream) {
  first2lines("withCloseable example 2", _)
}

Using the Loan Pattern

Let’s say we want to be able to write a family of File processing routines that use withCloseable to take care of the housekeeping (that is, the opening and closing of the File). The file contents are prefixed by a message that provides a clue as to what the contents are.

Here is an example of one of the File processing routines, which uses the first2lines method we just defined:

Scala REPL
scala> import java.io._
import java.io._
scala>
import PartiallyAppliedStuff._ import PartiallyAppliedMethods._
scala>
val tryContents1: Try[String] = withCloseable(fileInputStream) { first2lines("tryContents1", _) } tryContents1: Success(tryContents6: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin)

We can provide a different lambda function, which returns the first two lines of the file in upper case:

Scala REPL
scala> val tryContents2: Try[String] = withCloseable(fileInputStream) { first2lines("tryContents2", _).toUpperCase }
res13: scala.util.Try[String] =
Success(ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH
DAEMON:X:1:1:DAEMON:/USR/SBIN:/USR/SBIN/NOLOGIN) 

Our task is to make a partially applied function of the above, which would allow us to pass in any Function for arbitrary processing of the FileInputStream bound to the first parameter list. Let’s try the obvious thing and create openFileInputStream3 as a partially applied function which accepts a FileInputstream and returns a String. I called this partially applied function openFileInputStream3 and I invoked it by passing in a lambda function that invokes the first2lines method as before and then reverses the resulting String.

Scala REPL
scala> val openFileInputStream3 = withCloseable(fileInputStream)
                                               (_: FileInputStream => String)
openFileInputStream3: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1>
scala>
val tryContents3: Try[String] = openFileInputStream3 { first2lines("tryContents3", _).reverse } tryContents3: scala.util.Try[String] = Success(nigolon/nibs/rsu/:nibs/rsu/:nomead:1:1:x:nomead hsab/nib/:toor/:toor:0:0:x:toor :3stnetnoCyrt)

Eta Expansion Type Hints

The underscore at the end of the expression is optional if a type hint to the left of the equals sign indicates currying. Eta expansion happens in this case even if there is no trailing underscore.

Scala REPL
scala> val openFileInputStream4: (FileInputStream => String) => Try[String] =
  withCloseable(fileInputStream)
openFileInputStream4: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1>
scala>
val tryContents4: Try[String] = openFileInputStream4 { first2lines("tryContents4", _).toUpperCase.reverse } tryContents4: scala.util.Try[String] = Success(NIGOLON/NIBS/RSU/:NIBS/RSU/:NOMEAD:1:1:X:NOMEAD HSAB/NIB/:TOOR/:TOOR:0:0:X:TOOR :4STNETNOCYRT)

As we saw in the previous lecture, if we partially apply the withCloseable function by only providing it the first parameter list, like this:

Scala REPL
scala> val noGood = withCloseable(fileInputStream) _
noGood: (java.io.FileInputStream => Nothing) => scala.util.Try[Nothing] = <function1> 

... then unfortunately the returned type is Nothing. Because withCloseable is parametric, you must let the compiler know the type of the unbound method parameters. One way to do this is to provide a type hint on the left of the equals sign, as we saw with openFileInputStream4. The other way is to specify the parametric types for the method, like this.

Scala REPL
scala> val openFileInputStream5 =
  withCloseable[FileInputStream, String](fileInputStream) _
opened: (java.io.FileInputStream => String) => scala.util.Try[String] = <function1> 

The Nothing type is now replaced with String. Note that the type of the new openFileInputStream5 Function is (java.io.FileInputStream => String) => scala.util.Try[String].

Generating a Partially Applied Function From a Parametric Method

While it is good practice to assign FunctionNs to vals, sometimes we can simplify the creation of a partially applied functions by using def instead of val, because defs can be parametric, while vals cannot.

Eta Expansion Example

Scala REPL
scala> def openFileInputStream6[T] =
  withCloseable[FileInputStream, T](fileInputStream) _
openFileInputStream5: [T]=> (java.io.FileInputStream => T) => scala.util.Try[T] 

The openFileInputStream6 method is parametric in T. The body consists of an invocation of withCloseable, passing in two type parameters (FileInputStream as before and T, which will vary between invocations of this partially applied function generator). The other parameters are fileInputStream, and it ends in an underbar, which means that we want to leave the remaining parameters unbound, or in other words we want to create a partially applied function. Notice that the return type is Try[T].

Now we can invoke openFileInputStream6. I’ve provided a type hint on the left of the equals sign, which indicates that the return type must be Try[String], and the Scala compiler understands that means T must be a String for this partially applied function instance. The lambda passed to openFileInputStream6 consists of an invocation of first2lines, and shorthand notation is again used to indicate that the FileInputStream parameter passed to the lambda should be bound to the underscore. The results of first2lines are then further processed by the invocation of replace.

Scala REPL
scala> val tryContents6: Try[String] =
  openFileInputStream6 { first2lines("tryContents6", _).replace(":", "#") }
tryContents6: scala.util.Try[String] =
Success(tryContents6#
  root#x#0#0#root#/root#/bin/bash
  daemon#x#1#1#daemon#/usr/sbin#/usr/sbin/nologin) 

Placeholder Syntax Example

Here is another example, where a new partially applied function is returned from a method. withBufferedInputStream1 is parametric in T and it accepts an input of type java.io.File. The method body consists of an invocation of withCloseable, which as you know accepts two parameter lists, each of which consist of only one parameter. The first parameter is an instance of a BufferedInputStream, and the second parameter is a lambda function that is declared to receive the BufferedInputStream and return an instance of a T. The second parameter has no implementation, it is just a type declaration using placeholder syntax, which means that it is unbound and therefore this creates a partially applied function. In other words, withBufferedInputStream1 is another partially applied function generator.

Scala code
def withBufferedInputStream1[T](input: File) =
  withCloseable(new BufferedInputStream(new FileInputStream(input)))
               (_: BufferedInputStream => T)

This method returns an instance of a partially applied function with this signature: (java.io.BufferedInputStream => T) => scala.util.Try[T].

It is good to define the return type of a method, so here is a better way to write the same method. Again note that the trailing underscore was not necessary because of the highlighted type hint on the left hand side of the equals sign. Note also that this a curried type.

Scala code
def withBufferedInputStream[T](input: File): (BufferedInputStream => T) => Try[T] =
  withCloseable(new BufferedInputStream(new FileInputStream(input)))

We can invoke the partially applied Function1 returned by the withBufferedInputStream method like the following. Notice that the results from the withBufferedInputStream invocation are iterated through by foreach, and each result is printed.

Scala code
withBufferedInputStream(file) { first2lines("withBufferedInputStream", _) } foreach println

Output is:

Output
withBufferedInputStream:
  root:x:0:0:root:/root:/bin/bash
  daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

Nesting Partially Applied Functions

Let’s define withBuffferedOutputStream, which is the dual of withBufferedInputStream.

withBufferedOutputStream is a method that accepts a File instance and returns a partially applied FunctionN that is parametric in T, accepts a BufferedOutputStream instance and returns an instance of Try[T].

Scala code
def withBufferedOutputStream[T](output: File): (BufferedOutputStream => T) => Try[T] =
  withCloseable(new BufferedOutputStream(new FileOutputStream(output)))

The following code copies a BufferedInputStream instance to a BufferedOutputStream instance. Notice how the two partially applied functions generated from withBufferedInputStream and withBufferedOutputStream provide the result of executing their bound parameters to their respective lambda functions as inputStream and outputStream. We again use the read method shown earlier.

Scala code
withBufferedInputStream(file) { inputStream =>
  withBufferedOutputStream(new File("/tmp/blah")) { outputStream =>
    read(inputStream).foreach(outputStream.write(_))
  }
}

You will frequently see this pattern in library code.

Review Example 1

Partially applied FunctionNs can make any bound parameter available to a block of code. Here is an example of a method whose second parameter list accepts a function.

Scala REPL
scala> def stringMunger(a: String)(f: String => String) = f(a)
repeat: (a: String)(f: String => String)String 

stringMunger’s last parameter list is a function that accepts a String and returns a String. It is easy to make a partial function from a curried function – just leave off one or more of the trailing parameter lists and add an underscore.

Scala REPL
scala> val abc = stringMunger("abc") _
abc: (String => String) => String

A partially applied Function1 called abc was lifted from the stringMunger method by the _ operator. No argument was supplied for the second parameter, which is why this is a partially applied Function1.

The body of the lifted stringMunger method remains intact, even though we don’t see it in the line where abc is defined; however, the body of the partially applied function is still defined as f(a), just the same as stringMunger’s body.

As you know, it is in the body where the stringMunger method parameters (and the abc Function parameters) come together to perform a computation. The type of abc is (String => String) => String, which of course means it is a curried function.

The only parameter that is bound to the partially applied function abc is the String "abc", and it is bound to the first parameter, a. The body of the method (and the Function1 lifted from the method) invokes f(a) – in other words, it applies f to a. The second parameter list merely specifies what f is. We know from the stringMunger method body that f accepts a single String parameter, and this matches the type of f, which is declared in the second parameter list as String => String. Thus when we wish to employ the partially bound function abc we know that we will need to supply a function that accepts the String bound to a, and returns another String.

We could invoke the partially applied function by supplying a FunctionN with the proper method signature, or a regular method and Scala will automatically lift it into a FunctionN, but instead for now we will provide a lambda function. This lambda function names the parameter that receives the value of a as x, but we could have called it anything else.

Scala REPL
scala> abc { x => x * 4 }
res6: String = abcabcabcabc 

Notice that curly braces are used instead of parentheses around the code block { x => x * 4 }. Scala allows { curly braces } to be used instead of ( round parentheses ) if a parameter list has only one parameter. Using curly braces around a block of code makes it easier to read.

We can use the shorthand introduced in the Functions are First Class lecture of the Introduction to Scala course to rewrite the previous line as.

Scala REPL
scala> abc { _ * 4 }
res7: String = abcabcabcabc 

Review Example 2

This example shows a partially applied function that has all its data parameters bound, and is invoked by passed in a lambda to bind to remaining the unbound parameter. This allows you to apply arbitarary transformations to data at will. Knowledge of this technique can be an invaluable addition to your software programming repetoire.

Let’s define a method, parametric in T, that generates partially applied functions. twoStringMunger accepts three parameter lists, two of which are Strings and the third is a Function that accepts a Tuple2[String, String] and returns an instance of T. The third parameter, f, is applied to the first two parameters of type String, and returns an instance of T.

Scala REPL
scala> def twoStringMunger[T](a: String)(b: String)(f: (String, String) => T) = f(a, b)
twoStringMunger: (a: String)(b: String)(f: (String, String) => String)String 

Now we can use twoStringMunger as the basis for a new partially applied function called twoStringToIntPF, where T is defined as Int, a is bound to "abcdefghi", and b is bound to "def". The underbar at the end of this expression indicates to the Scala compiler that there are no more parameters to bind, so a partially applied function is created.

Scala REPL
scala> val twoStringToIntPF = twoStringMunger[Int]("abcdefghi")("def") _
abcdef: ((String, String) => Int) => Int = <function1> 

We can invoke twoStringToIntPF by providing an expression for the unbound parameter f. The method signature for the original twoStringMunger method defines the type of f as Tuple2[String, String] => T, and the definition of twoStringMunger declares T to be Int, so the Scala compiler will attempt to interpret the lambda provided to the partially applied function as having the method signature Tuple2[String, String] => Int.

That means that s1 and s2 must both be of type String, or there must be an implicit in scope that can convert them into Strings, and the expression s1.indexOf(s2) must either return an Int or there must be an implicit in scope that can convert the result of that expression into an Int.

Scala REPL
scala> twoStringToIntPF { (s1, s2) => s1.indexOf(s2) }
res8: Int = 3 

Review Example 3

This example is just like the previous example, but it uses a method lifted to a function instead of a lambda to bind the remaining unbound parameter of the partially bound function.

First we define a method that references the bound parameters a, b and f in the implementation. concatStringsAndFnValue is a curried method that accepts two Strings and a Function2 that accepts a Tuple2[String, String], and returns a String.

Scala REPL
scala> def concatStringsAndFnValue(a: String)
                                 (b: String)
                                 (f: (String, String) => String) = a + b + f(a, b)
another2StringMunger: (a: String)(b: String)(f: (String, String) => String)String 

We can invoke the method by supplying arguments for every parameter and obtain a result.

Scala REPL
scala> concatStringsAndFnValue("x")("y") { (u, v) => (u.length + v.length).toString }
res9: String = xy2 

We can also partially apply the method, binding parameters a and b, but not providing a binding for f, which means that parameter is unbound, and we have a partially applied function called xyHuh.

Scala REPL
scala> val xyHuh = concatStringsAndFnValue("x")("y") _
xyHuh: ((String, String) => String) => String = <function1> 

xyHuh is a partially applied functioN that has all its data parameters bound, and can only have a function passed in. Now we define a method called u2v3 that matches the signature for the f parameter of concatStringsAndFnValue.

Scala REPL
scala> def u2v3(u: String, v: String): String = (u.length * 2 + v.length * 3).toString
u2v3: (u: String, v: String)String 

When we provide the u2v3 method as the value of the f parameter, the Scala compiler lifts v2v3 method to a Function2.

Scala REPL
scala> val bing = xyHuh { u2v3 }
bing: String = xy5 

For practice, I suggest you try writing additional methods and functions that match the required signature, and invoke xyHuh with those transformations.

Exercise – Wrapping Database Operations With the Loan Pattern

Let’s use the loan pattern to write a database wrapper. JDBC connections implement AutoCloseable, not Closeable. We can modify withCloseable to work with JDBC connections either of the following two ways.

  1. Change the signature to accept the type AutoCloseable instead of Closeable.
    Scala code
    def withCloseable[C <: AutoCloseable, T](factory: => C)
                                            (operation: C => T): Try[T]
  2. Change the signature to accept a structural type that references a method called close which does not accept parameters and returns nothing. Structural types are a little slower than regular types because they are implemented with introspection.
    Scala code
    def withCloseable[C <: { def close(): Unit }, T](factory: => C)
                                                    (operation: C => T): Try[T]

It is up to you to decide how you want to modify withCloseable. In either case, the body of the withCloseable method would remain unchanged.

We’ll use the SQLite database, which just uses a single file to store the database and doesn’t have a concept of username or password. You will need to add the appropriate dependency in build.sbt. Here is the SQLite Git project on BitBucket.

You need to find the most recent version of that dependency by using https://mvnrepository.com and insert it into build.sbt. If you use an Scala-IDE, don’t forget to update the IDE project by typing sbt eclipse.

Your program only needs this single import statement to make the SQLite functionality available.

Scala code
import java.sql.{Connection, DriverManager}

You can get a connection to a SQLite database using the following.

Scala code
Class.forName("org.sqlite.JDBC")
def connection(url: String, userName: String="", password: String=""): Connection = DriverManager.getConnection(url, userName, password)

The SQLite database driver is a JDBC4-compatible driver. All JDBC 4+ drivers in the class path are automatically loaded. However, you must manually load any drivers prior to JDBC 4 with the method Class.forName. There is no harm in calling Class.forName, just to be sure.

The following will get a connection to a SQLite database in the current directory called person.db (remember that userName and password are ignored by SQLite).

Scala code
val conn = connection("jdbc:sqlite:person.db")

You can create a table with the following method:

Scala code
def createTable(connection: Connection, creationStatement: String)(implicit connection:Connection): Connection = {
  val statement = connection.createStatement()
  statement.setQueryTimeout(30)
  statement.executeUpdate(s"drop table if exists $tableName")
  statement.executeUpdate(s"create table $tableName ($creationStatement)")
  connection
}

Given a database connection called conn, you can create a table called person with columns id, name and age by writing:

Scala code
createTable("person", "id integer, name string, age integer", conn)

Of course, if an implicit Connection instance is in scope you could write:

Scala code
createTable("person", "id integer, name string, age integer")

The following will insert a new record into the database. Yes, Fred is 40,001 years old.

Scala code
val stmt = conn.prepareStatement("insert into person (id, name, age) values (?, ?, ?)")
stmt.setInt(1, 1)
stmt.setString(2, "Fred Flintstone")
stmt.setInt(3, 400001) // Cromagnon man!
stmt.executeUpdate()

Your task is to write a console application that uses the loan pattern to obtain a database connection and insert a few records, then select them and print them out.

Hints:

  1. You can select all records in a database like this:
    Scala code
    val stmt2 = conn.prepareStatement("select * from person")
  2. If you consider stmt2.executeQuery as a factory returning a resultSet, you could pass it to withCloseable and obtain each successive record by calling resultSet.next. ResultSets are mutable objects, which means they are not threadsafe, but they are convenient to work with in a loop.
  3. You might want to use a do while loop to walk through the entire resultSet.
  4. The number of columns in the resultSet can be obtained by calling resultSet.getMetaData.getColumnCount.
  5. The ith column in a resultSet can be obtained by calling resultSet.getObject(i).

Solution

The solutions project, a sibling of the courseNotes project, contains a solution.

  1. Start by cloning the sbtTemplate git project into a new directory called dbOps:
    Shell
    $ git clone https://github.com/mslinn/sbtTemplate dbOps
    $ cd dbOps
  2. Figure out the dependencies for SqlLite and added them to libraryDependencies in build.sbt. As of the date this solution was written, the most recent stable releases for the dependencies were:
    Shell
    libraryDependencies ++= Seq(
      "org.xerial"        %  "sqlite-jdbc"   % "3.14.2.1" withSources(),
      //
      "org.scalatest"     %% "scalatest"     % "3.0.0"  % "test" withSources(),
      "junit"             %  "junit"         % "4.12"   % "test" // Scala IDE requires this; IntelliJ IDEA does not
    )
  3. I opted to use the more flexible structural types approach for rewriting withCloseable, and defined a trait called Closeable to hold it:
    Scala code
    trait Closeable {
      //type Closer = AutoCloseable // this works fine, but there is a more flexible approach:
      /** Alias for structural type that has a method called close which does not accept parameters and returns nothing */
      type Closer = { def close(): Unit }
       /** Modified to handle Closeable or AutoCloseable by using a structural type */
      def withCloseable[C <: Closer, T](factory: => C)(operation: C => T): Try[T] = {
        val closeable = factory
        try {
          val result: T = operation(closeable)
          closeable.close()
          Success(result)
        } catch {
          case throwable: Throwable =>
            try { closeable.close() } catch { case _: Throwable => }
            println(throwable.toString)
            Failure(throwable)
        }
      }
    }
  4. I created a trait called DBOps to contain the database operation methods:
    Scala code
    trait DBOps extends Closeable {
      import java.sql.{Connection, DriverManager, ResultSet, Statement}
    import scala.collection.immutable.Map
    Class.forName("org.sqlite.JDBC")
    def connectTable(url: String, userName: String="", password: String=""): Connection = { implicit val conn = connection(url, userName, password) createTable("person", "id integer, name string, age integer") }
    def connection(url: String, userName: String="", password: String=""): Connection = DriverManager.getConnection(url, userName, password)
    def createTable(tableName: String, creationStatement: String)(implicit connection: Connection): Connection = { val statement = connection.createStatement statement.setQueryTimeout(30) statement.executeUpdate(s"drop table if exists $tableName") statement.executeUpdate(s"create table $tableName ($creationStatement)") connection }
    def insert(tableName: String, nameValueMap: Map[String, Any])(implicit conn: Connection) = { val keys = nameValueMap.keys.toList // nameValueMap.keys is an iterable, which can only be traversed once val names = keys.mkString(", ") val placeholders = keys.map { _ => "?" }.mkString(", ") val stmtString = s"insert into $tableName ($names) values ($placeholders)" val stmt = conn.prepareStatement(stmtString) nameValueMap.values.zipWithIndex foreach { case (value, i) => stmt.setObject(i+1, value) } stmt.executeUpdate() }
    /** Required to support statement.cancel() */ private var maybeCurrentStatement: Option[Statement] = None
    /** Must be run from another thread than execute */ def cancel(): Unit = { maybeCurrentStatement.foreach(_.cancel()) maybeCurrentStatement = None }
    /** Executes simple queries, which can be cancelled */ def execute(statementString:String)(body: ResultSet => Any)(implicit conn: Connection) = { withCloseable(conn.createStatement) { statement => maybeCurrentStatement = Some(statement) val rs = statement.executeQuery(statementString) withCloseable(rs) { resultSet => body(resultSet) } maybeCurrentStatement = None } } }
  5. Now writing the app is easy:
    Scala code
    object LoanDB extends App with Closeable with DBOps {
      withCloseable(connectTable("jdbc:sqlite:person.db")) { implicit conn: Connection =>
        insert("person", Map("id" -> 1, "name" -> "Fred Flintsone",  "age" -> 400002))
        insert("person", Map("id" -> 2, "name" -> "Wilma Flintsone", "age" -> 400001))
        insert("person", Map("id" -> 3, "name" -> "Barney Rubble",   "age" -> 400004))
        insert("person", Map("id" -> 4, "name" -> "Betty Rubble",    "age" -> 400003))
    val stmt2 = conn.prepareStatement("select * from person") withCloseable(stmt2.executeQuery) { resultSet => val columnCount: Int = resultSet.getMetaData.getColumnCount do { val x = (1 to columnCount).map(resultSet.getObject).mkString(", ") println(x) } while (resultSet.next) } } }

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.