代码之家  ›  专栏  ›  技术社区  ›  Daniel C. Sobral

scala有哪些自动资源管理替代方案?

  •  98
  • Daniel C. Sobral  · 技术社区  · 6 年前

    我在scala的Web上看到了许多ARM(自动资源管理)的例子。写一篇文章似乎是一种仪式,尽管大多数看起来非常相似。我 不过,请看一个使用continuations的非常酷的例子。

    不管怎样,很多代码都有一种或另一种类型的缺陷,所以我认为最好在堆栈溢出时在这里有一个引用,在这里我们可以投票选出最正确和合适的版本。

    7 回复  |  直到 6 年前
        1
  •  60
  •   jsuereth    14 年前

    丹尼尔,

    我最近刚部署了scala arm库用于自动资源管理。您可以在这里找到文档: http://wiki.github.com/jsuereth/scala-arm/

    此库支持三种使用样式(当前):

    1)命令式/表示式:

    import resource._
    for(input <- managed(new FileInputStream("test.txt")) {
    // Code that uses the input as a FileInputStream
    }
    

    2)一元风格

    import resource._
    import java.io._
    val lines = for { input <- managed(new FileInputStream("test.txt"))
                      val bufferedReader = new BufferedReader(new InputStreamReader(input)) 
                      line <- makeBufferedReaderLineIterator(bufferedReader)
                    } yield line.trim()
    lines foreach println
    

    3)分隔的延续样式

    这是一个“echo”TCP服务器:

    import java.io._
    import util.continuations._
    import resource._
    def each_line_from(r : BufferedReader) : String @suspendable =
      shift { k =>
        var line = r.readLine
        while(line != null) {
          k(line)
          line = r.readLine
        }
      }
    reset {
      val server = managed(new ServerSocket(8007)) !
      while(true) {
        // This reset is not needed, however the  below denotes a "flow" of execution that can be deferred.
        // One can envision an asynchronous execuction model that would support the exact same semantics as below.
        reset {
          val connection = managed(server.accept) !
          val output = managed(connection.getOutputStream) !
          val input = managed(connection.getInputStream) !
          val writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output)))
          val reader = new BufferedReader(new InputStreamReader(input))
          writer.println(each_line_from(reader))
          writer.flush()
        }
      }
    }
    

    代码使用资源类型特征,因此它能够适应大多数资源类型。它有一个回退,可以对具有Close或Dispose方法的类使用结构类型。请查看文档,如果您想添加任何方便的功能,请告诉我。

        2
  •  72
  •   huynhjl    15 年前

    克里斯·汉森的 blog entry 'ARM Blocks in Scala: Revisited' from 3/26/09 谈论马丁·奥德斯基的第21张幻灯片 FOSDEM presentation . 下一个块直接从幻灯片21中获取(经许可):

    def using[T <: { def close() }]
        (resource: T)
        (block: T => Unit) 
    {
      try {
        block(resource)
      } finally {
        if (resource != null) resource.close()
      }
    }
    

    ——结束引用——

    然后我们可以这样称呼:

    using(new BufferedReader(new FileReader("file"))) { r =>
      var count = 0
      while (r.readLine != null) count += 1
      println(count)
    }
    

    这种方法的缺点是什么?这种模式似乎可以解决95%的我需要自动资源管理的地方…

    编辑: 添加了代码段


    编辑2: 扩展设计模式-从python汲取灵感 with 声明和地址:

    • 要在块之前运行的语句
    • 根据托管资源重新引发异常
    • 使用一条单独的using语句处理两个资源
    • 通过提供隐式转换和 Managed

    这是2.8级的。

    trait Managed[T] {
      def onEnter(): T
      def onExit(t:Throwable = null): Unit
      def attempt(block: => Unit): Unit = {
        try { block } finally {}
      }
    }
    
    def using[T <: Any](managed: Managed[T])(block: T => Unit) {
      val resource = managed.onEnter()
      var exception = false
      try { block(resource) } catch  {
        case t:Throwable => exception = true; managed.onExit(t)
      } finally {
        if (!exception) managed.onExit()
      }
    }
    
    def using[T <: Any, U <: Any]
        (managed1: Managed[T], managed2: Managed[U])
        (block: T => U => Unit) {
      using[T](managed1) { r =>
        using[U](managed2) { s => block(r)(s) }
      }
    }
    
    class ManagedOS(out:OutputStream) extends Managed[OutputStream] {
      def onEnter(): OutputStream = out
      def onExit(t:Throwable = null): Unit = {
        attempt(out.close())
        if (t != null) throw t
      }
    }
    class ManagedIS(in:InputStream) extends Managed[InputStream] {
      def onEnter(): InputStream = in
      def onExit(t:Throwable = null): Unit = {
        attempt(in.close())
        if (t != null) throw t
      }
    }
    
    implicit def os2managed(out:OutputStream): Managed[OutputStream] = {
      return new ManagedOS(out)
    }
    implicit def is2managed(in:InputStream): Managed[InputStream] = {
      return new ManagedIS(in)
    }
    
    def main(args:Array[String]): Unit = {
      using(new FileInputStream("foo.txt"), new FileOutputStream("bar.txt")) { 
        in => out =>
        Iterator continually { in.read() } takeWhile( _ != -1) foreach { 
          out.write(_) 
        }
      }
    }
    
        3
  •  18
  •   Daniel C. Sobral    7 年前

    这里是 James Iry 使用延续的解决方案:

    // standard using block definition
    def using[X <: {def close()}, A](resource : X)(f : X => A) = {
       try {
         f(resource)
       } finally {
         resource.close()
       }
    }
    
    // A DC version of 'using' 
    def resource[X <: {def close()}, B](res : X) = shift(using[X, B](res))
    
    // some sugar for reset
    def withResources[A, C](x : => A @cps[A, C]) = reset{x}
    

    以下是具有或不具有可供比较的连续性的解决方案:

    def copyFileCPS = using(new BufferedReader(new FileReader("test.txt"))) {
      reader => {
       using(new BufferedWriter(new FileWriter("test_copy.txt"))) {
          writer => {
            var line = reader.readLine
            var count = 0
            while (line != null) {
              count += 1
              writer.write(line)
              writer.newLine
              line = reader.readLine
            }
            count
          }
        }
      }
    }
    
    def copyFileDC = withResources {
      val reader = resource[BufferedReader,Int](new BufferedReader(new FileReader("test.txt")))
      val writer = resource[BufferedWriter,Int](new BufferedWriter(new FileWriter("test_copy.txt")))
      var line = reader.readLine
      var count = 0
      while(line != null) {
        count += 1
        writer write line
        writer.newLine
        line = reader.readLine
      }
      count
    }
    

    下面是Tiark Rompf的改进建议:

    trait ContextType[B]
    def forceContextType[B]: ContextType[B] = null
    
    // A DC version of 'using'
    def resource[X <: {def close()}, B: ContextType](res : X): X @cps[B,B] = shift(using[X, B](res))
    
    // some sugar for reset
    def withResources[A](x : => A @cps[A, A]) = reset{x}
    
    // and now use our new lib
    def copyFileDC = withResources {
     implicit val _ = forceContextType[Int]
     val reader = resource(new BufferedReader(new FileReader("test.txt")))
     val writer = resource(new BufferedWriter(new FileWriter("test_copy.txt")))
     var line = reader.readLine
     var count = 0
     while(line != null) {
       count += 1
       writer write line
       writer.newLine
       line = reader.readLine
     }
     count
    }
    
        4
  •  7
  •   Joannes Vermorel Jonathan McIntire    6 年前

    我看到了在scala中进行ARM的逐步4步进化:

    1. 无臂:污垢
    2. 只有闭包:更好,但有多个嵌套块
    3. continue monad:用于平整嵌套,但在2个块中不自然分离
    4. 直接风格延续:涅拉瓦,啊哈!这也是最安全的类型选择:具有资源块的外部资源将是类型错误。
        5
  •  6
  •   pathikrit    9 年前

    有重量轻(10行代码)手臂包括更好的文件。见: https://github.com/pathikrit/better-files#lightweight-arm

    import better.files._
    for {
      in <- inputStream.autoClosed
      out <- outputStream.autoClosed
    } in.pipeTo(out)
    // The input and output streams are auto-closed once out of scope
    

    如果您不想要整个库,可以通过以下方式实现:

      type Closeable = {
        def close(): Unit
      }
    
      type ManagedResource[A <: Closeable] = Traversable[A]
    
      implicit class CloseableOps[A <: Closeable](resource: A) {        
        def autoClosed: ManagedResource[A] = new Traversable[A] {
          override def foreach[U](f: A => U) = try {
            f(resource)
          } finally {
            resource.close()
          }
        }
      }
    
        6
  •  1
  •   Santhosh Sath    7 年前

    使用类型类怎么样

    trait GenericDisposable[-T] {
       def dispose(v:T):Unit
    }
    ...
    
    def using[T,U](r:T)(block:T => U)(implicit disp:GenericDisposable[T]):U = try {
       block(r)
    } finally { 
       Option(r).foreach { r => disp.dispose(r) } 
    }
    
        7
  •  1
  •   ChoppyTheLumberjack    7 年前

    另一个选择是斩波的懒惰的Tryclose Monad。它非常适合数据库连接:

    val ds = new JdbcDataSource()
    val output = for {
      conn  <- TryClose(ds.getConnection())
      ps    <- TryClose(conn.prepareStatement("select * from MyTable"))
      rs    <- TryClose.wrap(ps.executeQuery())
    } yield wrap(extractResult(rs))
    
    // Note that Nothing will actually be done until 'resolve' is called
    output.resolve match {
        case Success(result) => // Do something
        case Failure(e) =>      // Handle Stuff
    }
    

    有了溪流:

    val output = for {
      outputStream      <- TryClose(new ByteArrayOutputStream())
      gzipOutputStream  <- TryClose(new GZIPOutputStream(outputStream))
      _                 <- TryClose.wrap(gzipOutputStream.write(content))
    } yield wrap({gzipOutputStream.flush(); outputStream.toByteArray})
    
    output.resolve.unwrap match {
      case Success(bytes) => // process result
      case Failure(e) => // handle exception
    }
    

    更多信息在这里: https://github.com/choppythelumberjack/tryclose

    推荐文章