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 03:47
2018/04/06 03:55 編集
2018/04/06 04:01
2018/04/06 04:34