kotlinでの変数制約設計
6866 ワード
可変状態の氾濫は往々にしてソフトウェアメンテナンス災害の元凶の一つとされ、特にプロセスパッケージがマルチスレッドに遭遇すると、一般的なオブジェクト向けプログラミング技術は完全に役に立たない.継承/パッケージ/マルチステートなどの手法はプログラム組織上の処理措置であり、具体的には下層実現上、従来のC/C++/JAVAは依然としてプロセスに依存してオペレーティングシステムとの付き合いを実現している.
関数式プログラミングにおける副作用
関数式プログラミングの世界では、従来のプロセス式の処理が異なるようになります.ここでは関数自体が副作用があるかどうかにこだわっているので、同じ入力で同じ出力を保証できないと、副作用があります.ここでの出力は、戻り値だけでなく、環境への影響も様々に隠されています.申請したが解放されなかったメモリ は、ネットワークソケット のような共有リソースの占有をオペレーティングシステムに要求する.画面出力、ディスク占有など なぜ副作用を区別するのか
副作用はプログラマのメンテナンスを必要とする追加のステータスを導入し,従来のスレッドライブラリまたは基本的なOSメカニズムはプログラマに完全に任せていることは明らかである.これにより,マルチスレッドプログラミング環境では,複雑な問題が状態の増加に伴って指数関数的に上昇する.ステータスは、共有リソースのメンテナンスが必要であることを意味し、同時実行のプロセスやスレッドがある場合、正しいプログラムの意味を保証するために、ロック(高価な操作)と競争を導入し、パフォーマンスを制約しなければならない.ロックレスアルゴリズムはCAS+再試行機構により,ロックのオーバーヘッドを部分的に緩和できるが,本質的に問題を解決することはできない.
副作用のない関数は天然に合併に適している.共有がなければ自然に並行して実行できるので、問題は完璧に解決されたのではないか.しかし、現実世界では絶対完璧な二文字が存在することは許されず、純粋に副作用のない関数はほとんど役に立たない.本質的には何の役にも立たないので、何もできないからだ.
次の考えは、できるだけ両者の実現を隔離し、優雅に両者を統合して実際の機能を完成させることができるかどうかだ.HASKELLはその優雅なmonad抽象でこの質問に答えた.しかし、抽象的な思考能力がそんなに強くない(あるいはそんなに良い数学の基礎がない)プログラマーにとって、Monadは本当に陽春白雪で近づきにくい.もっと接地したいプログラム言語は、いくつかの構造や設計の思想がMonadに由来していても、例えばどこにでも見られるOptional、基本的なmap/reduceチェーン操作など、Monadと距離を保つことを選択しないことはありません.
monadの導入を示さないこれらの非純粋な関数言語にとって,厳格な隔離はあまりにも急進的である.代わりに、相対的に折衷された平凡な戦略は、言語メカニズム自体がいくつかの基礎メカニズムを提供し、残りはどのようにこれらの基本メカニズムを使用するか、すべてプログラマー自身が決定することです.
kotlinの言語レベルの基本メカニズム
kotlinは、キーワード
次の例を考えます.
読み取り専用変数の初期化
明らかに可変変数は一度だけ初期化でき,その後の使用では変更できない.これにより、例えば
コア集合クラス
kotlinはJAVAからの集合クラスライブラリを二次カプセル化し,読み取り専用集合クラスと可変集合を明確に区分した.
インタフェース定義
一般的な集合クラスインタフェースは
特定の集合クラスインタフェースは、上記のインタフェースから対応するものを選択して実装を拡張するため、同じタイプに対して読み取り専用(接頭辞なし)と可変タイプ(Mutableを接頭辞として区別)の2つの実装がある.例えば
なお、実際の実装クラスは多重Javaでの定義であり、collectionパッケージの
デフォルトのコレクション操作およびStreams APIが返すほとんどは、可変インタフェースオブジェクトです.
コレクションクラス拡張/ツール関数
デフォルトのJDKインプリメンテーションを使用して特定のクラスオブジェクトを生成する以外に、Kotlin標準ライブラリには、プログラマが使用するのに便利なパッケージ関数が多数用意されています.JDKの直接パッケージに由来するものもあれば、直接inlineインプリメンテーションに由来するものもあります.
たとえば空のlistのパッケージを返したり、いろいろなlistを初期化したりします.
可変Listを生成する関数パッケージの多くも明瞭であり、多くの種類のパッケージがあり、その場でListを生成する作業を大幅に簡素化する.ほとんどの場合、既存の関数を使用するだけでよく、新しいホイールを発明する必要はありません.
他の集合クラス(set/mapなど)の実装原理は概ね類似しており、対応するソースコードを表示することができる.
可変集合を可変集合に変換
多くのシーンでは、APIは可変集合を返し、それを可変オブジェクトにしてから行編集を変更するのはよくあるタスクになります.kotlinは、独自の拡張メカニズムによって、これらのツール関数を対応する集合クラスに自動的に追加します.
読み取り専用の
特定のArrayクラスでは、
なぜなら、これらのサブクラスは、特定のJVMオブジェクトにマッピングされるからである.ByteArrayのドキュメントのように
public final class ByteArray defined in kotlin An array of bytes. When targeting the JVM, instances of this class are represented as
一方、CharArrayについては、
IDEAサポート
公式のIDE環境として、IDEAは変数の参照に下線を表示し、プログラマーはコード内の変数の使用を一目瞭然に見ることができる.
しかし、実装呼び出しチェーン全体で、副作用が導入されていないものをより深く表示するには、ツールのサポートが限られています.
関数式プログラミングにおける副作用
関数式プログラミングの世界では、従来のプロセス式の処理が異なるようになります.ここでは関数自体が副作用があるかどうかにこだわっているので、同じ入力で同じ出力を保証できないと、副作用があります.ここでの出力は、戻り値だけでなく、環境への影響も様々に隠されています.
副作用はプログラマのメンテナンスを必要とする追加のステータスを導入し,従来のスレッドライブラリまたは基本的なOSメカニズムはプログラマに完全に任せていることは明らかである.これにより,マルチスレッドプログラミング環境では,複雑な問題が状態の増加に伴って指数関数的に上昇する.ステータスは、共有リソースのメンテナンスが必要であることを意味し、同時実行のプロセスやスレッドがある場合、正しいプログラムの意味を保証するために、ロック(高価な操作)と競争を導入し、パフォーマンスを制約しなければならない.ロックレスアルゴリズムはCAS+再試行機構により,ロックのオーバーヘッドを部分的に緩和できるが,本質的に問題を解決することはできない.
副作用のない関数は天然に合併に適している.共有がなければ自然に並行して実行できるので、問題は完璧に解決されたのではないか.しかし、現実世界では絶対完璧な二文字が存在することは許されず、純粋に副作用のない関数はほとんど役に立たない.本質的には何の役にも立たないので、何もできないからだ.
次の考えは、できるだけ両者の実現を隔離し、優雅に両者を統合して実際の機能を完成させることができるかどうかだ.HASKELLはその優雅なmonad抽象でこの質問に答えた.しかし、抽象的な思考能力がそんなに強くない(あるいはそんなに良い数学の基礎がない)プログラマーにとって、Monadは本当に陽春白雪で近づきにくい.もっと接地したいプログラム言語は、いくつかの構造や設計の思想がMonadに由来していても、例えばどこにでも見られるOptional、基本的なmap/reduceチェーン操作など、Monadと距離を保つことを選択しないことはありません.
monadの導入を示さないこれらの非純粋な関数言語にとって,厳格な隔離はあまりにも急進的である.代わりに、相対的に折衷された平凡な戦略は、言語メカニズム自体がいくつかの基礎メカニズムを提供し、残りはどのようにこれらの基本メカニズムを使用するか、すべてプログラマー自身が決定することです.
kotlinの言語レベルの基本メカニズム
kotlinは、キーワード
val
によって読み取り専用の変数を宣言し、var
によって変数を宣言する.任意の関数は、変数の使用を導入する限り、それ自体が明らかな副作用を有する.しかし、1つの変数は読み取り専用として宣言され、対応する役割ドメインにおいて、この変数の値を変更することは許されず、実際に指向されるデータオブジェクト自体が可変であることを意味しない.なぜなら、var
を操作するために他の場所で使用される可能性があるため、または表示される方法でval
の変数を可変var
に変換するためである.次の例を考えます.
// field1 , class
class SomeClass(val field1 : SomeType, var field2 : Int) {
fun doSth() {
// can only modify field2, but not field1
}
}
//calling site
var someTypeInst = SomeType()
val obj = SomeClass(someTypeInst, 112)
// someTypeInst can still be changed by others! Not recommended!
obj.doSth()
someTypeInst
は、obj
に読み取り専用で送信されるが、他のスレッドが実際のオブジェクトを同時に変更することは保証されず、この場合、プログラマはデータの一貫性と安全性を保証する必要がある.読み取り専用変数の初期化
明らかに可変変数は一度だけ初期化でき,その後の使用では変更できない.これにより、例えば
init block
でリソースを一度に初期化してclass内部で読み取り専用に設定しようとすると、何もできません.1つの融通性のある方法は、var
クラスに設定することですが、読み取り専用制約を失うことになります.もう1つの方法はproperty構造を使用してカプセル化する必要がある.コア集合クラス
kotlinはJAVAからの集合クラスライブラリを二次カプセル化し,読み取り専用集合クラスと可変集合を明確に区分した.
インタフェース定義
一般的な集合クラスインタフェースは
kotlin,collections
パケットで再定義される(ソースコードではCollections.kt
にある)package kotlin.collections
//...
// by default not mutable
public interface Iterable {//... }
// mutable iterable supports removing elements during iterating
public interface MutableIterable : Iterable {//...}
//Only read access to collection
public interface Collection : Iterable {//...}
// Supports read/write operations
public interface MutableCollection : Collection, MutableIterable {//...}
特定の集合クラスインタフェースは、上記のインタフェースから対応するものを選択して実装を拡張するため、同じタイプに対して読み取り専用(接頭辞なし)と可変タイプ(Mutableを接頭辞として区別)の2つの実装がある.例えば
List
類は// Read only list interface
public interface List : Collection {//...}
// Mutable list
public interface MutableList : List, MutableCollection {//...}
なお、実際の実装クラスは多重Javaでの定義であり、collectionパッケージの
TypeAliases.kt
ファイルを参照することができるpackage kotlin.collections
//...
@SinceKotlin("1.1") public typealias ArrayList = java.util.ArrayList
デフォルトのコレクション操作およびStreams APIが返すほとんどは、可変インタフェースオブジェクトです.
コレクションクラス拡張/ツール関数
デフォルトのJDKインプリメンテーションを使用して特定のクラスオブジェクトを生成する以外に、Kotlin標準ライブラリには、プログラマが使用するのに便利なパッケージ関数が多数用意されています.JDKの直接パッケージに由来するものもあれば、直接inlineインプリメンテーションに由来するものもあります.
たとえば空のlistのパッケージを返したり、いろいろなlistを初期化したりします.
/** Returns an empty read-only list. */
public fun emptyList(): List = EmptyList
/** Returns a new read-only list of given elements. */
public fun listOf(vararg elements: T): List = if (elements.size > 0) elements.asList() else emptyList()
/** Returns an empty read-only list. */
@kotlin.internal.InlineOnly
public inline fun listOf(): List = emptyList()
/**
* Returns an immutable list containing only the specified object [element].
* The returned list is serializable.
*/
@JvmVersion
public fun listOf(element: T): List = java.util.Collections.singletonList(element)
可変Listを生成する関数パッケージの多くも明瞭であり、多くの種類のパッケージがあり、その場でListを生成する作業を大幅に簡素化する.ほとんどの場合、既存の関数を使用するだけでよく、新しいホイールを発明する必要はありません.
/** Returns an empty new [MutableList]. */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun mutableListOf(): MutableList = ArrayList()
/** Returns an empty new [ArrayList]. */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun arrayListOf(): ArrayList = ArrayList()
/** Returns a new [MutableList] with the given elements. */
public fun mutableListOf(vararg elements: T): MutableList
= if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))
他の集合クラス(set/mapなど)の実装原理は概ね類似しており、対応するソースコードを表示することができる.
可変集合を可変集合に変換
多くのシーンでは、APIは可変集合を返し、それを可変オブジェクトにしてから行編集を変更するのはよくあるタスクになります.kotlinは、独自の拡張メカニズムによって、これらのツール関数を対応する集合クラスに自動的に追加します.
読み取り専用の
Array
オブジェクトを可変のMutableList
オブジェクトに変更する場合、そのインプリメンテーションは、新しいオブジェクトを再初期化することによって実現される.// Below code is copied from generated standlib as _Arrays.kt
// see https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib
/**
* Returns a [MutableList] filled with all elements of this array.
*/
public fun Array.toMutableList(): MutableList {
return ArrayList(this.asCollection())
}
特定のArrayクラスでは、
ByteArray
の初期化方法のように異なる実装があり、その構造関数を直接呼び出し、既存の各要素の追加に注意します./**
* Returns a [MutableList] filled with all elements of this array.
*/
public fun ByteArray.toMutableList(): MutableList {
val list = ArrayList(size)
for (item in this) list.add(item)
return list
}
なぜなら、これらのサブクラスは、特定のJVMオブジェクトにマッピングされるからである.ByteArrayのドキュメントのように
public final class ByteArray defined in kotlin An array of bytes. When targeting the JVM, instances of this class are represented as
byte[]
. 一方、CharArrayについては、
char []
型にマッピングされる.IDEAサポート
公式のIDE環境として、IDEAは変数の参照に下線を表示し、プログラマーはコード内の変数の使用を一目瞭然に見ることができる.
しかし、実装呼び出しチェーン全体で、副作用が導入されていないものをより深く表示するには、ツールのサポートが限られています.