challenge loan patternのサンプル

リソースを使うときのパターンloanのサンプルを書いて下さい。
参考
Programming in Scala (P.170 , P.172)

妙なところに投稿してしまっていたようです。すみません。 ネットワークプログラムは不慣れなところがありますので、ビシィっと指導してくださいませ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//loan patternのサンプル
//お題:http://ja.doukaku.org/258/

import java.net._

object LoanSample {
  def main(args : Array[String]) : Unit = {
    SocketLoan.processWith("ja.doukaku.org",80){
      sock=>
        println( sock )
        // ここにいろいろな処理
        // そして、たまに、間違う
        var a = 1/0 // ---> java.lang.ArithmeticException
        // ここにいろいろな処理
        ()
    }
  }
}

object SocketLoan{
  def processWith(serverName:String,port:Int)(op:Socket=>Unit) : Unit = {
    try{
      val sock = new Socket(serverName,port)
      //-------------------------------
      try{
        // loan it
        op(sock)
      }catch{
        case e:Exception => println("err inside loan : "+e)
        case _ =>
      }
      //-------------------------------
      sock.close()
    }catch{
      case e:Exception => println("err outside loan"+ e)
      case _ =>
    }
  }
}

Posted feedbacks - Flatten

Nested Hidden

妙なところに投稿してしまっていたようです。すみません。 ネットワークプログラムは不慣れなところがありますので、ビシィっと指導してくださいませ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//loan patternのサンプル
//お題:http://ja.doukaku.org/258/

import java.net._

object LoanSample {
  def main(args : Array[String]) : Unit = {
    SocketLoan.processWith("ja.doukaku.org",80){
      sock=>
        println( sock )
        // ここにいろいろな処理
        // そして、たまに、間違う
        var a = 1/0 // ---> java.lang.ArithmeticException
        // ここにいろいろな処理
        ()
    }
  }
}

object SocketLoan{
  def processWith(serverName:String,port:Int)(op:Socket=>Unit) : Unit = {
    try{
      val sock = new Socket(serverName,port)
      //-------------------------------
      try{
        // loan it
        op(sock)
      }catch{
        case e:Exception => println("err inside loan : "+e)
        case _ =>
      }
      //-------------------------------
      sock.close()
    }catch{
      case e:Exception => println("err outside loan"+ e)
      case _ =>
    }
  }
}

「リソースを解放しよう」という感じでOK?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using system;
using System.IO;
class P
{
    static void Main()
    {
        using (var sr = new StreamReader("file-location"))
        {
            //うんちゃらかんちゃら
            Console.WriteLine (sr.ReadToEnd());
            //例外発生!
            throw new Exception();
            //でも、大丈夫
        }
    }
}

そうです。 組み込みの構文があるといいですね。 低レベルのAPIを使うときに、有効なパターンではなかろうかと。


loan pattern 用の汎用関数です。
引数なしのコンストラクタを持ち、IDisposableインターフェースを実装するクラスに対して使用可能です。

<実行結果>
処理実行
破棄
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#light

open System

let loan (f : 'T -> 'U when 'T : (new : unit -> 'T) and 'T :> IDisposable) =
    use x = new 'T() in f x

type Example() =
    member this.Method() = printfn "処理実行"
    interface IDisposable with
        member this.Dispose() = printfn "破棄"

loan (fun (x : Example) -> x.Method())

少し、ひねってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.net._

object LoanSample {
  def main(args : Array[String]) : Unit = {
    SocketLoan.usingSocketOf(
               ("ja.doukaku.org",80),
               ("http://ja.doukaku.org/",80), //<---勘違い
               ("\\.org",80), // <---たまに、異常なデータ
               ("labs.cybozu.co.jp",80) ){ (serverName,port,sock) =>
        println( sock )
        // ここにいろいろな処理
        // そして、たまに、間違う
        var a = 1/0 // ---> java.lang.ArithmeticException
        // ここにいろいろな処理
        ()
    }
  }
}

object SocketLoan{
  def usingSocketOf(hosts:(String,Int)*)( op:(String,Int,Socket)=>Unit ):Unit={
    for((serverName,port)<-hosts){
      println("using "+ serverName)
      usingSocketOf(serverName,port)(op(serverName,port,_))
      println("used "+ serverName)
    }
  }
  def usingSocketOf(serverName:String,port:Int)(op:Socket=>Unit) : Unit = {
    try{
      println("opening")
      val sock = new Socket(serverName,port)
      println("opened")
      //-------------------------------
      try{
        // loan it
        op(sock)
      }catch{
        case e:Exception => println("err inside loan : "+e)
        case _ =>
      }
      //-------------------------------
      println("closing")
      sock.close()
      println("closed")
    }catch{
      case e:Exception => println("err outside loan"+ e)
      case _ =>
    }
  }
}

メモリマップをFileダンプにつかってみました。 (バッファをloanする)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.io._
import java.nio._
import java.nio.channels._

object LoanSample{
  def main(args : Array[String]) : Unit = {
    FileLoan.viewFileAsBuffer(args(0)){ byteBuffer =>
        //      "012345678:---
        val maxPos  = byteBuffer.limit()
        var viewLine=""
        println("          0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F")
        for{ i <-0 until byteBuffer.limit() by 16
             j <-0 until 16
             pos = i + j }{
          if(j== 0){
            print(pos.formatted("%08X: "))
            viewLine = ""
          }
          if( pos<byteBuffer.limit() ){
            val b = byteBuffer.get(pos)
            val c = b.toChar
            print( b.formatted("%02X ") )
            viewLine += (if( c.isControl ) "." else ""+c)
          }else{
            print(                                "    "  )
            viewLine += " "
          }
          if(j==15){
            println( viewLine )
            viewLine = ""
          }
        }
        ()
    }
  }
}

object FileLoan{
  def viewFileAsBuffer(fileName:String)(op:ByteBuffer=>Unit) : Unit = {
    var channel:FileChannel = null
    var buffer :ByteBuffer  = null
    try{
      channel = new FileInputStream(fileName).getChannel()
      buffer  = channel.map(FileChannel.MapMode.READ_ONLY , 0 , channel.size())
      op( buffer )
    }catch{
      case e:Exception =>{
          println(channel,buffer)
          println(e)
        }
      case _ =>
    }finally{
      buffer  = null
      channel = null
    }
  }
}

channel (またはFileInputStream) のclose()を忘れているかと思います。

C#/VB.NETのusingに憧れて作った、Scala用のusing構文です。
IDisposableのような統一的な解放インターフェースが無いので、closeメソッドやdisposeメソッドを持つオブジェクトを対象にします。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.io._

object UsingSyntax {
  
  trait Disposing[-A] extends (A => Unit)
  
  // closeメソッドを持つオブジェクトの解放方法を定義
  implicit val closeDefinedDisposing = new Disposing[{def close(): Unit}] {
    def apply(target: {def close(): Unit}) = target.close()
  }
  // disposeメソッドを持つオブジェクトの解放方法を定義
  implicit val disposeDefinedDisposing = new Disposing[{def dispose(): Unit}] {
    def apply(target: {def dispose(): Unit}) = target.dispose()
  }
  // 自分で解放方法を指定する場合の変換
  // using (obj) { 処理... } { (_: MyDisposable).myDispose() }
  implicit def functionToDisposing[A](f: A => Any): Disposing[A] =
  new Disposing[A] {
    def apply(target: A): Unit = f(target)
  }
  
  def using[A, R](a: A)(block: A => R)(implicit disposing: Disposing[A]): R = {
    try {
      block(a)
    }
    finally {
      disposing(a)
    }
  }
  
}

object Doukaku258 {
  import UsingSyntax._
  
  def main(args: Array[String]): Unit = {
    
    // ファイルからテキストを読み込んで全部表示
    // 使い終わったらファイルを閉じる
    using (new FileInputStream("test.txt")) { in =>
      using (new InputStreamReader(in, "UTF-8")) { rd =>
        
        val buf = new Array[Char](512)
        var n = 0
        while ({n = rd.read(buf); n > 0}) {
          print(buf.take(n).mkString)
        }
        
        // 処理中に例外が発生してもファイルは閉じる
        throw new Exception()
        
      }
    }
    
  }
  
}

nullをいれたら、後始末してくれるのかと思ってました。

・・・

Java/Scalaマスター への道は遠いなあ。


C++では広義のRAIIと呼ばれる技法です。基本はデストラクタです。ここでは、リソースとしてfopen/fcloseの対を用いました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cstdio>
#include <stdexcept>

class FilePtr
{
public:
    FilePtr(std::FILE* filePointer) : fp(filePointer)
    {
        if (fp == 0)
        {
            throw std::invalid_argument("filePointer == 0");
        }
    }
    ~FilePtr() {std::fclose(fp);} // デストラクタ
    std::FILE* get() {return fp;}
private:
    std::FILE* fp;
    FilePtr(const FilePtr&);
    FilePtr& operator =(const FilePtr&);
};

int main()
{
    FilePtr f(std::fopen("hoge.txt", "w"));
    std::fputs("Hello, world\n", f.get());
} // ここでfのデストラクタが呼ばれる。

まあ、使い回しするならクラスにまとめるのもいいですが、そうでないなら即席的に作る方法も色々あります。これ、shared_ptrは本来メモリ用ですが、解放に使う関数オブジェクトを指定できるので、だいたい何にでも使用できます。参照カウントというおまけ付きですが。
1
2
3
4
5
6
7
8
9
#include <cstdio>
#include <memory> // または<boost/shared_ptr.hpp>

int main()
{
    std::tr1::shared_ptr<std::FILE> f(std::fopen("hoge.txt", "w"), std::fclose);
    // またはboost::shared_ptr
    std::fputs("Hello, world\n", f.get());
}

BOOST_SCOPE_EXITでも紹介しようかなと思いましたが、本家っぽい感じがしたD言語で書いてみました。いずれにせよ、スコープを抜けるときに対象の文を実行するというものという点では同じです。
1
2
3
4
5
6
7
8
9
import std.c.stdio;

int main()
{
    FILE* fp = fopen("hoge.txt", "w");
    scope(exit) fclose(fp);
    fputs("hello, world", fp);
    return 0;
}

Javaにはクロージャがないので、匿名クラスを使ってみました。まず、要件を以下と考えました。

・ブロックを抜ける時に強制的にリソースの解放を行う。
・リソースを使う箇所では解放を意識しない。

サンプルでは、mainメソッドの中身がリソースを使う箇所になります。匿名クラスなので構文的には複雑になっていますが、要件は満たしていると思います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.io.*;

public class WithResource<T extends Closeable> {

    private T resource;
    
    public WithResource(T resource) {
        this.resource = resource;
    }
    
    public T get() {
        return resource;
    }

    public void using(Runnable f) throws IOException {
        try {
            f.run();
        } finally {
            resource.close();
        }
    }

    public static void main(String[] args) throws IOException {
        final WithResource<PrintWriter> wr = new WithResource<PrintWriter>(
                new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))));
        wr.using(new Runnable() {
            public void run() {
                wr.get().print("この文字列はflushしないと書き出されない");
                wr.get().print("(closeし忘れは良くある間違い)。");
            }
        });
    }

}

Squeak Smalltalk で。

第一パラメータで与えた名前のファイルを開き、第二パラメータで与えた処理を行い正常・異常に関わらず終了時にファイルを閉じる #fileNamed:do: というメソッドがおそらくこのパターンのサンプルになっていると思います。

1
FileStream fileNamed: 'test.txt' do: [:file | #doSomething]

Java版(#9162)をScalaに移植しました。 これはこれで味わい深いです。 ありがとうございました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Java版#9162を移植
import java.io._

case class WithResource[T<:Closeable]( resource:T ) {
    def using[F<:Runnable](f:F) : Unit= {
        try {
            f.run();
        } finally {
            resource.close();
        }
    }
}

object Main{
    def main(args:Array[String]) : Unit = {
        val wr = WithResource(  new PrintWriter(
                                new BufferedWriter(
                                new OutputStreamWriter(System.out))))
        wr.using(new Runnable{
                    override def run():Unit = {
                        wr.resource.print("この文字列はflushしないと書き出されない");
                        wr.resource.print("(closeし忘れは良くある間違い)。");
                    } } )
    }
}

 Rubyで。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#! /usr/bin/ruby

class FileLoan
  def self.with_resource(path, mode)
    begin
      f = File.open(path, mode)
      begin
        yield f
      ensure
        f.close
      end
    rescue => ex
      puts ex.message
    end
  end
end

FileLoan.with_resource("test.txt", "r") do |f|
  f << "test"
  f.flush
end

 perlがまだの様なので。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#! /usr/bin/perl

package FileLoan;

use strict;

sub with_resource {
    my    ($pkg, $path, $mode, $encoding, $closure) = @_;
    my    $file;
    open ($file, "${mode}:encoding(${encoding})", $path) || die "cannot open file.";
    eval {
        &${closure}($file);
    };
    close($file);
    die $@ if $@;
}

package main;

eval {
    FileLoan->with_resource("test.txt", ">", "utf8", sub {
        my    $f = shift;
        print $f "test\n";
    });
};
print $@ if $@;

1;

1
2
with open('test.txt') as f:
  print f.read()

Groovyではloanパターンは標準ライブラリでサポートされてますので,利用側の例として一つ。

1
2
3
4
5
6
new Socket("www.google.com", 80).withStreams { ins, outs ->
  outs.write("GET / HTTP/1.0\n\n".getBytes())
  ins.eachLine {
    println it
  }
}

Index

Feed

Other

Link

Pathtraq

loading...