質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

1回答

3180閲覧

kotlin の Closeable のネストが深くなってしまう

mosa

総合スコア218

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2018/04/06 00:16

いつもありがとうございます。

kotlin の use は便利なのですがネストが深くなってしまうことがあります。
例えばJavaではこう書くところを

Java

1try( 2 Socket soket = new Socket(host, port); 3 InputStream input = soket.getInputStream(); 4 OutputStream output = soket.getOutputStream() 5){ 6 // 何かの処理 7}

kotlinでは

Kotlin

1Socket(host, port).use { socket -> 2 socket.getInputStream().use { input -> 3 socket.getOutputStream().use { output -> 4 // 何かの処理 5 } 6 } 7}

こうなります。

Javaに比べてネストが深くなってしまってなんだか気持ち悪いのですが、この書き方であっているのでしょうか。
(一般的なのでしょうか)
関数わければいいじゃんといわれればそのとおりなのですが・・・。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

kotlinには暗いのですがLoanパターンが多重になっているのを「ネストを深くしないで記述」することを考えてみました。(知識不足な点があるかと思います。違ってる点があったらご容赦を)

Javaのtry-with-resourceの多重版は

Java

1try (A a = ...) { 2 try (B b = ...) { 3 try (C c = ...) { 4 ... 5 } 6 } 7}

の略記記法ですが、それが行うことを考えると

(1A) Aの値を計算
(1B) (1A)の結果をaに束縛したtry文脈の中で以下を行う
(2A) Bの値を計算
(2B) a,および(2A)の結果をbに束縛したtry文脈の中で以下を行う
...
(nA) a, b, ...を束縛したtry文脈の中で何か計算する

という感じですね?

関数プログラミングでは関数が第一級オブジェクトとして扱える点を用いて「文法を拡張するのではなく」「なんらかの関数で目的のことを達成する」ことを狙うと思います。そういう意味で上記のような多重Loanパターンを関数プログラミング的(use的?)に解きほぐすなら「バインドすべき値を求める」部分(=上記の(iA))と「最終的に全てをバインドした文脈下で行うべきこと」の部分(=上記の(nB))を引数で与えることを考えると思います。(iB|i<n)の部分はuseが達成しようとする「処理が煩雑で呼び出し側が一々書きたくない部分」なので共通関数側でなんとかしたいですものね。

さて多重useが関数として用意されていたとするとこんなインターフェースになるのかなと思います。

inline <T: Closeable?, A: Closeable?, B: Closeable?, R> T.use( bindingA: (T) -> A, bindingB: (T, A) -> B, // (※1) block: (T, A, B) -> R): R

任意の多重度に対してvarargなどを用いて単一の関数定義でできたらいいのですが、どうしてもAやBのそれぞれの型を特定できないと使い物にならないため(自分がわかる限りでは)束縛すべき変数の数に応じたuseの定義を別個に用意しないといけない気がしました。

※1: ここは(T) -> Bあるいは(A) -> Bでもいいんじゃないかという話があるかも知れません。そうしたい場合は(T)->Bと(A)->B用にそれぞれオーバーロード定義をすればいいのかも知れませんが、下手にやると場合によってオーバーロードが解決できない(呼び出しの仕方によってはどちらのオーバーロード定義を用いるべきか曖昧になる)おそれがなきにしも有らずと思います。

こういうものがKotlinのライブラリーに用意されているかどうかを見てみますと、少なくともkotlin.ioパッケージを見た限りでは見当たりませんでした。

http://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/index.html

もし定義するとしたらこんな感じではないかと思います。

Kotlin

1inline fun <T: Closeable?, A: Closeable?, B: Closeable?, R> T.use( 2 bindingA: (T) -> A, bindingB: (T, A) -> B, block: (T, A, B) -> R): R { 3 return use { t -> 4 bindingA(t).use { a -> 5 bindingB(t, a).use { 6 block(t, a, it) 7 } 8 } 9 } 10} 11 12// 使い方(ネストが4ではなく8になってるのはIDEAが自動インデントしたままにしたからです) 13Socket(host, port).use( 14 { it.getInputStream() }, 15 { socket, _ -> socket.getOutputStream() }, 16 { socket, i, o -> 17 // 何かの処理 18 })

また、考え方を変えて「Socketに対してこういう処理パターンが多いからもう少し特化した定義にしちゃえ」とするなら以下のような定義もありなのかなーと思います。いかがでしょうか・・・

kotlin

1inline fun <R> Socket.use(block: (Socket, InputStream, OutputStream) -> R): R { 2 return this.use { socket -> 3 getInputStream().use { input -> 4 getOutputStream().use { 5 block(socket, input, it) 6 } 7 } 8 } 9} 10 11// 使い方 12... 13Socket(host, port).use { socket, input, output -> 14 // 何かの処理 15}

上記は標準ライブラリーにない機能でも定義しちゃえという考えでコメントしてますが、場合によっては「拡張関数定義の乱用問題」に繋がるかも知れません。アプリケーションを個人でしか書かない自分の場合は割と気楽にやってしまいますが、チームで開発する際にはこうした定義をすることについてメンバーと同意を取らないといけないのだろうと思います。

投稿2018/04/06 02:34

編集2018/04/06 02:36
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mosa

2018/04/06 03:47

ありがとうございます! 聞きたかったのはまさに「Loanパターンが多重になってしまう」です。 わかりやすく説明して頂いてありがとうございます。 ただ、1点だけ追加で質問させてください。 「※1」の部分はちゃんと理解できていないのか、やっぱり「(T) -> B」でもいいんじゃないかと思ってしまいます。 実際今回のSocketのケースだけであれば問題なく動く?と思います。
KSwordOfHaste

2018/04/06 03:55 編集

標準ライブラリーを設計する人は try (InputStream is = new FileInputStream(...);  InputStreamReader rd = new InputStreamReader(is, StandardCharset.UTF_8);  BufferedReader br = new BufferedReader(rd)) {  ... } このような定義が使えないような汎用useを定義したいとは思わないと思います。(T)->Bでよいと思うのは「そういうことがたまたましたいアプリケーション」では都合がよいですが上記のようなことがしたい場合は使えない関数になってしまいますよね?
KSwordOfHaste

2018/04/06 04:01

あ・・・自分で定義するならmosaさんがおっしゃるように(T)->Bで定義したってもちろんよいと思いますよ?ただ充分汎用にはならないということが上のコメントの趣旨です。自分の回答の2番目にかいたようなものはそういう意味でメッチャ特化定義になってますね。 あまり特化しすぎると汎用性が損なわれ、あまり一般化しすぎると実際に使う際にやりにくいという両面があるのでバランスが難しいですね。 JavaでもStream#collect(Collectors.toList())はえらくメンドクサイので「なんでStream#toList()を用意してくれないのかなぁ・・・他の言語にはあるのに」なんて思う人は多いのではないでしょか。この設計がいいのか不足なのかは意見がわかれるかも知れませんが。
mosa

2018/04/06 04:34

具体的な例をいただきありがとうございます。理解できました。 お付き合いいただきありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問