Scala에는 어떤 자동 리소스 관리 대안이 있습니까?


102

웹에서 Scala 용 ARM (자동 리소스 관리) 예제를 많이 보았습니다. 대부분은 서로 비슷해 보이지만 하나를 쓰는 것은 통과 의례 인 것 같습니다. 나는 한 하지만, 연속 요청을 사용하여 정말 멋진 예제를 참조하십시오.

여하튼, 그 코드의 상당수는 한 유형 또는 다른 유형의 결함을 가지고 있으므로 가장 정확하고 적절한 버전을 투표 할 수있는 Stack Overflow에 대한 참조를 여기에 갖는 것이 좋을 것이라고 생각했습니다.


이 질문이 커뮤니티 위키가 아니라면 더 많은 답변을 생성할까요? 커뮤니티 위키 상 평판에서 답변을 투표했는지 확인하십시오 ...
— huynhjl 2010-02-07

2
고유 참조는 리소스에 대한 참조가 close ()가 호출되기 전에 관리자에게 반환되도록 ARM에 다른 수준의 안전을 추가 할 수 있습니다. thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
— retronym

@retronym 고유성 플러그인은 계속되는 것보다 훨씬 더 많은 혁명이 될 것이라고 생각합니다. 그리고 사실 저는 이것이 스칼라에서 그리 멀지 않은 미래에 다른 언어로 이식 될 가능성이 높은 것 중 하나라고 생각합니다. 이것이 나오면 그에 따라 답변을 편집합시다. :-)
— Daniel C. Sobral

1
여러 java.lang.AutoCloseable 인스턴스를 중첩 할 수 있어야하므로 각 인스턴스는 성공적으로 인스턴스화하는 이전 인스턴스에 의존하므로 마침내 저에게 매우 유용한 패턴을 발견했습니다. 비슷한 StackOverflow 질문에 대한 답변으로 작성했습니다. stackoverflow.com/a/34277491/501113
— chaotic3quilibrium

답변:


10

지금은 스칼라 2.13 마침내 지원했습니다 try with resources사용하여 사용 :) 예 :

val lines: Try[Seq[String]] =
  Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

또는 Using.resource회피 사용Try

val lines: Seq[String] =
  Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

Using doc 에서 더 많은 예제를 찾을 수 있습니다 .

자동 리소스 관리를 수행하는 유틸리티입니다. 리소스를 사용하여 작업을 수행하는 데 사용할 수 있으며 그 후에 리소스를 생성 한 순서와 반대로 해제합니다.


Using.resource변형도 추가해 주 시겠습니까?
— Daniel C. Sobral

@ DanielC.Sobral, 물론, 방금 추가했습니다.
— chengpohi 19

Scala 2.12 용으로 어떻게 작성 하시겠습니까? 다음은 유사한 using방법입니다.def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
— Mike Slinn 19.07.09

75

Chris Hansen의 블로그 항목 'ARM Blocks in Scala : Revisited'(09 년 3 월 26 일)에서 Martin Odersky의 FOSDEM 프레젠테이션 슬라이드 21에 대해 설명합니다. 합니다. 다음 블록은 슬라이드 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 %를 해결하는 것 같습니다.

편집 : 코드 스 니펫 추가


Edit2 : 디자인 패턴 확장 -Pythonwith 문 에서 영감을 얻고 주소 지정 :

  • 블록 전에 실행할 문
  • 관리 자원에 따라 예외 재발생
  • 하나의 using 문으로 두 개의 리소스 처리
  • 암시 적 변환 및 Managed클래스를 제공하여 리소스 별 처리

이것은 Scala 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(_) 
    }
  }
}

2
대안이 있지만 그에 문제가 있음을 암시하는 것은 아닙니다. Stack Overflow에서 모든 답변을 원합니다. :-)
— Daniel C. Sobral

5
표준 API에 이와 같은 것이 있는지 알고 있습니까? 항상 나 자신을 위해이 글을 써야하는 집안일처럼 보인다.
— Daniel Darabos 2014 년

이것이 게시 된 이후로 오래되었지만 첫 번째 솔루션은 out 생성자가 던지면 내부 스트림을 닫지 않지만 여기에서 발생하지 않을 수 있지만 이것이 나쁠 수있는 다른 경우가 있습니다. 닫기도 던질 수 있습니다. 치명적인 예외도 구분하지 않습니다. 두 번째는 모든 곳에서 코드 냄새가 나고 첫 번째에 비해 이점이 없습니다. 실제 유형을 잃어 버리면 ZipInputStream과 같은 것에 쓸모가 없습니다.
— steinybot

블록이 반복자를 반환하는 경우이를 수행하는 방법을 권장합니까?
— Jorge Machado

62

다니엘,

최근에 자동 리소스 관리를 위해 scala-arm 라이브러리를 배포했습니다. 여기에서 문서를 찾을 수 있습니다 : https://github.com/jsuereth/scala-arm/wiki

이 라이브러리는 세 가지 사용 스타일을 지원합니다 (현재).

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 메서드를 사용하여 클래스에 대해 구조적 유형을 사용하는 대체 방법이 있습니다. 추가 할 편리한 기능이 있으면 설명서를 확인하고 알려주십시오.


1
네, 봤어요. 코드를 살펴보고 어떤 일을 어떻게 수행하는지보고 싶지만 지금은 너무 바빠요. 어쨌든 질문의 목표는 신뢰할 수있는 ARM 코드에 대한 참조를 제공하는 것이기 때문에 나는 이것을 받아 들인 대답으로 만들고 있습니다.
— Daniel C. Sobral

18

연속을 사용하는 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
}

(new BufferedWriter (new FileWriter ( "test_copy.txt")))를 사용하면 BufferedWriter 생성자가 실패 할 때 문제가 발생하지 않습니까? 모든 리소스는 using 블록으로 래핑되어야합니다 ...
— Jaap

@Jaap 이것은 Oracle에서 제안한 스타일 입니다. BufferedWriter확인 된 예외가 발생하지 않으므로 예외가 발생하면 프로그램이 복구되지 않습니다.
— Daniel C. Sobral 2013-08-28

7

Scala에서 ARM을 수행하기위한 점진적인 4 단계 진화를 봅니다.

  1. 팔 없음 : 먼지
  2. 클로저 만 : 더 좋지만 다중 중첩 블록
  3. Continuation Monad : For를 사용하여 중첩을 평평하게하지만 2 블록으로 부 자연스럽게 분리됩니다.
  4. 직접적인 스타일 연속 : Nirava, 아하! 이것은 또한 가장 유형이 안전한 대안입니다. withResource 블록 외부의 리소스는 유형 오류입니다.

1
Scala의 CPS는 모나드를 통해 구현됩니다. :-)
— Daniel C. Sobral

1
Mushtaq, 3) 연속 모나드가 아닌 모나드에서 리소스 관리를 수행 할 수 있습니다. 4) withResources / 리소스로 구분 된 연속 코드를 사용하는 리소스 관리는 "사용"보다 유형이 안전하지 않습니다. 필요한 리소스를 관리하는 것을 잊을 수 있습니다. 비교 using (new Resource ()) {first => val second = new Resource () // oops! // 리소스 사용} // 먼저 리소스를 사용하여 닫힘 {val first = resource (new Resource ()) val second = new Resource () // 죄송합니다! // 리소스 사용 ...} // 첫 번째 만 종료
— James Iry

2
Daniel, Scala의 CPS는 모든 기능적 언어의 CPS와 같습니다. 모나드를 사용하는 구분 된 연속입니다.
— James Iry

James, 잘 설명 해주셔서 감사합니다. 인도에 앉아 나는 당신의 BASE 강연을 위해 거기에 있었으면 좋겠어요. 그 슬라이드를 온라인에 올릴 때를 기다리고 있습니다. :)
— Mushtaq Ahmed

6

더 나은 파일에 포함 된 경량 (10 줄의 코드) ARM이 있습니다. 참조 : 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()
      }
    }
  }

이것은 꽤 좋습니다. 이 접근 방식과 비슷한 것을 취했지만 foreach 대신 CloseableOps에 대해 map및 flatMap메서드를 정의하여 이해를 위해 순회 가능을 생성하지 않습니다.
— EdgeCaseBerg

1

Type 클래스 사용 방법

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

1

또 다른 대안은 Choppy의 Lazy TryClose 모나드입니다. 데이터베이스 연결에 꽤 좋습니다.

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


0

다음은 @chengpohi의 답변이며 Scala 2.13 대신 Scala 2.8+에서 작동하도록 수정되었습니다 (예, Scala 2.13에서도 작동 함).

def unfold[A, S](start: S)(op: S => Option[(A, S)]): List[A] =
  Iterator
    .iterate(op(start))(_.flatMap{ case (_, s) => op(s) })
    .map(_.map(_._1))
    .takeWhile(_.isDefined)
    .flatten
    .toList

def using[A <: AutoCloseable, B](resource: A)
                                (block: A => B): B =
  try block(resource) finally resource.close()

val lines: Seq[String] =
  using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.