Closeable monad in Scala

March 10, 2013

import java.io.Closeable

case class Closer[C <: Closeable, A](unsafeRun: C => A) {

  def run(c: C): A = {
    val a = unsafeRun(c)
    println("***CLOSING***")
    c.close()
    a
  }
}

object Closer {

  implicit def closerMonad[C <: Closeable]: Monad[({ type λ[ɑ] = Closer[C, ɑ] })#λ] =
    new Monad[({ type λ[ɑ] = Closer[C, ɑ] })#λ] {

      def pure[A](a: A): Closer[C, A] =
        Closer(_ => a)

      def flatMap[A,B](ma: Closer[C, A])(f: A => Closer[C, B]): Closer[C, B] =
        Closer { c: C =>
          val a: A = ma.unsafeRun(c)
          f(a).unsafeRun(c)
        }
    }

  implicit def function1[C <: Closeable, A](f: C => A): Closer[C, A] =
    new Closer(f)
}

Dependencies

Examples

For the following examples, we'll use some common functions.

import java.io.RandomAccessFile
import Closer._
import Monad._
// opens a RandomAccessFile for reading
def file = {
  println("***OPENING***")
  new RandomAccessFile("jabberwocky.txt", "r")
}

// reads (if possible) a line from a RandomAccessFile, and sets up for reading
// the next line (if necessary)
val cat: RandomAccessFile => Stream[String] =
  { x =>
    println("***READING LINE***")
    Option(x.readLine()) match {
      case None       => Stream.empty
      case Some(line) => Stream(line) #::: cat(x)
    }
  }

We can do this with a bunch of calls to map...

// TODO: Unnecessary with Scala 2.13.0-M5 (33478bd)
type RAFCloser[A] = Closer[RandomAccessFile, A]

val closer: RAFCloser[Stream[String]] = cat

val closer1 = closer map { _.headOption } map { _ foreach println }
closer1 run file

/* Output:
***OPENING***
***READING LINE***
'Twas brillig, and the slithy toves
***CLOSING***
*/

...or with a for comprehension.

val closer2 =
  for {
    stream <- closer
     lineO  = stream.headOption
         _  = lineO map println
  } yield Unit
closer2 run file

/* Output:
***OPENING***
***READING LINE***
'Twas brillig, and the slithy toves
***CLOSING***
*/

This acts like the Unix head command.

val closer3 =
  for {
    stream <- closer
     lines  = stream.take(4)
          _ = lines foreach println
  } yield Unit
closer3 run file

/* Output:
***OPENING***
***READING LINE***
'Twas brillig, and the slithy toves
***READING LINE***
Did gyre and gimble in the wabe;
***READING LINE***
All mimsy were the borogoves,
***READING LINE***
And the mome raths outgrabe.
***CLOSING***
*/

Here we lazily read the file and print each line as we go.

val closer4 = closer map { _ foreach println }
closer4 run file

/*
***OPENING***
***READING LINE***
'Twas brillig, and the slithy toves
***READING LINE***
Did gyre and gimble in the wabe;
***READING LINE***
All mimsy were the borogoves,
***READING LINE***
And the mome raths outgrabe.
***READING LINE***
***CLOSING***
*/

With this we can lazily read the file, print each line, then print each line again without re-reading the file.

val closer5 =
  for {
    stream <- closer
         _  = stream foreach println
         _  = stream foreach println
    } yield Unit
closer5 run file

/*
***OPENING***
***READING LINE***
'Twas brillig, and the slithy toves
***READING LINE***
Did gyre and gimble in the wabe;
***READING LINE***
All mimsy were the borogoves,
***READING LINE***
And the mome raths outgrabe.
***READING LINE***
'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.
***CLOSING***
*/