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에 대해 mapflatMap메서드를 정의하여 이해를 위해 순회 가능을 생성하지 않습니다.
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.