カテゴリー: kotlin

こういうのをもっと楽にできないのだろうか(´・ω・`)?

こんな感じのやつ(´・ω・`)


disp
package org.example
import org.other.samplefunc.dosomething
import org.other.samplefunc.*

fun main(){
println("Hello kotlin!");
}




ちなみにここに至る流れは、

Kotlin Koans、これきちんと勉強しないと解けないやつやんけヽ(`Д´)ノ
 ↓
でも全部翻訳するのも時間がかかりすぎるし、基本部分だけ読んでなんか作るかぁ…
 ↓
取り敢えず読む
 ↓
(#^ω^)イライラ
 ↓
って、なんで相変わらず、プログラミング言語の解説ページは紙ベース、HTML以上の進歩がないんだよヽ(`Д´)ノ
 ↓
スニペットと文章による説明って、紙の書籍とかPCというか大きいディスプレイで読む分にはなんとかなるけど、スマホとかだとお話にならんじゃろがいヽ(`Д´)ノ ←老眼や老化のせいで小さい字が読みにくいというのもある("・ω・゙)
 ↓
断片的で体系的な学習を阻害する部分もある。
 ↓
ということで、標準的な一個のプログラムを提示し、説明したい各要素に触れることによって説明文等が表示出来る新しいフォーマットがあるべきじゃろヽ(`Д´)ノ
 ↓
だけど僕にはオツムがない~(・∀・)♪ ←新しいフォーマットを考える頭も気力もない
 ↓
まあ、aタグでtitle属性に書けば一応実現できるけどねぇ…( ゜σ・゚)ホジホジ
 ↓
スマホだと上手く動かない? onstarttouchを使えばいいのかもしれないがaタグだとインテントが飛んでそれをキャンセルする手間が発生する? 加えて言うと出来れば説明文自体は他所から持って来たい。
 ↓
取り敢えず、スマホ操作時に影響がない表示の実現をcssで図る。
CSSだけでツールチップを表示する
 ↓
だがしかし、説明文の分離とフェッチを俺氏のcss知識ではどうやっていいかわからない(´・ω・`)
 ↓
cssは諦めて、なんやかんやでjQueryで分離も可能に。そこまでしてないけれども、説明文は別ページに置いておいたり、もう少しidとclassをまとめることも出来そう。
 ↓
何故かdivでx方向のスクロールが出来ないと勘違いし、一時、iframeをいじくったり、srcdoc属性でjQuertyが使えないかと色々やってはまる。(正確に言うとwordpressのエントリ上のiframeのsrcdoc属性でjQueryを使おうとしていた。)
結局divで出来ることに気づいてiframeのことは忘れることにした(ノ∀`)
 ↓
キーワードの色付とか出来ないかなと思って色々奮闘したが、いまいち希望通りの形にならず、同時に「あれ? これ説明文を作る時に色付けとかすればいいのか(´・ω・`)?」と気付き、取り敢えず、
当初の新フォーマット的なものの提示だけしとこう(・∀・)ということに。

今回のものはコードが対象だけれども、これってもう一歩進めてあらゆる文書、解説書のみならず、小説とかでも語句の意味や時代背景とかを埋め込んで表示させるとかも出来ると思うし、やるべきなんではなかろうかと思う。英語文書の日本語訳埋め込みとかでもいいし。

ちなみに今回、上に表示divを配置したのはスマホで見た時に下に表示divがあると指で隠れてしまうという致命的欠陥があることに気づいた為(ノ∀`)

何はともあれ、
こういったことが簡単にできるフォーマットなり、統一的な決まりが作られて、より電子機器の利点がより活かされる流れになってほしい(・∀・)

もしかして俺氏が寡聞なだけで既にあるけど流行ってないとかなのだろうか…(´・ω・`)?

演算子のオーバーロード: kotlin

演算子のオーバーロード

Kotlinでは作成する型に対して、事前定義済である演算子セットのオーバーロード実装を提供できます。これらの演算子は固定の記号表現(+や*など)と固定の優先順位を持っています。

演算子をオーバーロード実装するには、該当する型(二項演算時の左側の型や単項演算時の引数の型)に対して既定の名前を持つメンバ関数または拡張関数を用意します。

この演算子をオーバーロードする関数はoperator修飾子を付加する必要があります

※一文目から超訳を発動してしまった(ノ∀`)
"Kotlin allows us to provide implementations for a predefined set of operators on our types."の"our type"を、主語が"Kotlin"であり、我々(プログラマ側)の型と表現していることから"(プログラマが)作成する型"としたが、Kotlinが定義している全ての型で演算子オーバーロードが出来るなら"任意の型"とすべきか?

さらに、さまざまな演算子の演算子のオーバーロードを制限する規約について説明します。


単項演算

単項接頭演算子

対応する関数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

この表は、コンパイラがたとえば式 +a を処理する時に以下の手順を実行することを示しています。

- aの型を決定します。Tとします。

- operator修飾子があり、引数がないレシーバT用の関数 unaryPlus()(即ちメンバー関数または拡張関数)を検索します。

- 関数が存在しないかあいまいな場合は、コンパイルエラーになります。

- 関数が存在し、その戻り値の型がRである場合、式+aの型はRです。

これらの演算は他のすべての演算と同様に基本型用に最適化されており、その関数呼び出しのオーバーヘッドを発生させないことに留意してください。

一例として、単項マイナス演算子をオーバーロードする方法は以下の通りです。:

data class Point(val x: Int, val y: Int)
​
operator fun Point.unaryMinus() = Point(-x, -y)
​
val point = Point(10, 20)
​
fun main() {
   println(-point)  // "Point(x=-10, y=-20)"を出力します。
}

インクリメント及びデクリメント

※よくわからないが、"-"を2つ並べて記述すると1つしか表示されない?
wordpressかプラグインによってまとめられて罫線扱いになっている? 取り敢えず<code>タグで囲んだが…
----- ←ソースだと"-"x5

※wptexturizeの所為だった(ノ∀`) WordPressで一部の文字が勝手に置換されるのを無効化(123x456→123×456とか)

対応する関数
a++ a.inc() + 以下を参照
a-- a.dec() + 以下を参照

inc()およびdec()関数は値を返す必要があります。この値は++演算または--演算で使用された変数に割り当てられます。incまたはdecは自身を呼び出したオブジェクトを変更してはなりません。

コンパイラは後置形式の演算子(例えばa++)を処理するために以下の手順を実行します。:

- aの型を決定します。Tとします。

- 型Tのレシーバに適用可能な、operator修飾子があり、引数がない
関数inc()(即ちメンバー関数または拡張関数)を検索します。;

- 関数の戻り値の型がTのサブ型であることを判定します。

式を計算することによる作用は以下の通りです。

- aの初期値を一時保存の a0に格納します。

- a0.inc()の結果をaに割り当てます。

- 式の結果としてa0を返します。※後置インクリなのでaの初期値を返している。

a--の場合では処理は完全に類似しています

前置形式 ++aおよびa--の場合には処理は同じように機能し、作用は以下の通りです。:

- a.inc()の結果をaに割り当てます。

- 式の結果として、aの新しい値を返します。


二項演算

算術演算子

対応する関数
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b), a.mod(b) (非推奨)
a..b a.rangeTo(b)

この表の演算の場合、コンパイラーは「対応する関数」列の式を処理するだけです。

Kotlin 1.1以降、rem演算子がサポートされていることに注意してください。Kotlin 1.0は、Kotlin1.1で非推奨となったmod演算子を使用します。


以下は、指定された値で始まり、オーバーロードされた+演算子を使用してインクリメントできるCounterクラスの例です。:

data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}

'In' 演算子

対応する関数
a in b b.contains(a)
a !in b !b.contains(a)

inと!inの処理過程は同じですが、引数の順序が逆になっています。
※集団側を指す引数に対して関数定義をするってことを言いたいのかな?


インデックス付きアクセス演算子

対応する関数
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ..., i_n] a.get(i_1, ..., i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)

角括弧は、適切な数の引数を使用してget()およびset()の呼び出しに変換されます。


呼び出し演算子

対応する関数
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)

括弧は、適切な数の引数を使用してinvoke()の呼び出しに変換されます。


拡張された代入(複合代入演算子?)

対応する関数
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b), a.modAssign(b) (非推奨)

複合代入演算の場合(例:a += b)、コンパイラは以下の手順を実行します。:

-右欄の関数が利用できる場合

-対応する二項関数(例えばplusAssign()であったらplus())も使用できる場合は、エラー(あいまいさ)を報告します。

ー戻り値の型がUnitであることを確認し、そうでない場合はエラーを報告します。

ーa.plusAssign(b);のコードを生成します。

それ以外の場合は、a = a + bのコードを生成しようとします。(これには型チェックが含まれます。a + bの型はaのサブ型である必要があります。)

注:Kotlinでは代入は式ではありません。


等価演算子および不等式演算子

対応する関数
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

これらの演算子は、関数 equals(other: Any?): Booleanでのみ機能します。これをオーバーライドしてカスタムの等価性判定の実装を提供できます。同じ名前の他の関数(equals(other: Foo)など)は呼び出されません。

注:===および!==(同一性判定)はオーバーロードできないため、これらの規則はありません。

==演算は特別です。nullを排除する複雑な式に変換されます。 null == nullは常にtrueであり、null以外のxのx == nullは常にfalseであり、x.equals()を呼び出しません。

screen 遮る、守る、篩い分ける、排除する


比較演算子

対応する関数
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

すべての比較は、Intを返す必要があるCompareTo()の呼び出しに変換されます。


プロパティ移譲演算子

ProvideDelegate、getValueおよびsetValue演算子関数は、委任されたプロパティで説明されています。


名前付き関数のInfix呼び出し

中置関数呼び出しを使用して、カスタムの中置演算を装うことができます。

※以下のように自作の関数を二項演算子として書けるようにできるらしい。

infix fun Int.shl(x: Int): Int { ... }
​
// infix記法を使用した関数呼び出しは、
1 shl 2
​
// 以下と同じです。
1.shl(2)

infix 接中辞、中置き型
infix operator 二項演算子
infix notation (omitting the dot and the parentheses for the call)
中置記法(呼び出しのドットと括弧を省略)

エクステンション(拡張) : kotlin

※以下のコードは全て"Target platform: JVMRunning on kotlin v. 1.4.20"のもの。元ページと同じように表記させるのが面倒なのでここで表記するw

※いつも通りの原文無視がちの超訳が多くなった。なんか原文の表現がわかりにくいので適当に超訳したが、勘違いしてる部分も多々あると思う(ノ∀`)

※修飾子(qualifier)の言葉の使い方がちょっと理解出来ない。「オブジェクト.メソッド()」という時にオブジェクトをメソッド()の修飾子と呼ぶのだろうか? 修飾語句でもなんか変だし。加えて言うとmodifierとの違いは何だろうか?


Extensions

Kotlinはクラス継承やDecoratorなどのデザインパターンを使用することなく、新しい機能によってクラスを拡張する能力を提供します。これはエクステンション(拡張/拡張機能)と呼ばれる特別な宣言を介して行われます。

たとえば、内容を変更することができないサードパーティのライブラリのクラスに対して新しい関数を記述できます。このような関数は元のクラスのメソッドであるかのように通常の方法で呼び出すことができます。

このメカニズムは拡張関数と呼ばれます。既存のクラスに対して新しいプロパティを定義できる拡張プロパティもあります。

拡張関数

拡張関数を宣言するには、レシーバ(追加を受ける、つまり拡張したい対象)の型名の後ろに追加する関数名と処理を記述します。以下はMutableListにswap関数を追加する例です。: swap 交換する

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this'は追加先のMutableListを指します。
    this[index1] = this[index2]
    this[index2] = tmp
}

拡張関数内のthisキーワードは、レシーバオブジェクト(ドットの前に記述された型のオブジェクト)を指します。以下のように記述することで任意のMutableListで定義した関数を呼び出すことができます。:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // swap()内の'this'は 'list'の値を保持します.

もちろん、この関数はどのMutableListにも意味があり、ジェネリクスにすることができます。:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this'はそのlistを指します。
    this[index1] = this[index2]
    this[index2] = tmp
}

レシーバ型の式で使用できるように、関数名の前にジェネリクスの型パラメーターを宣言します。詳細はジェネリクス関数を参照してください。
※この場合、レシーバ型名の前じゃないか? 関数名はあくまでもswapではなかろうか?


拡張機能は静的に解決されます

※なんか難しかったんで、俺氏超訳発動(`・ω・´)シェルダン

拡張機能は拡張するクラスを実際には変更しません。拡張機能を定義することによりクラスに新しいメンバ関数を挿入するのではなく、単に定義した関数をこの型の変数においてドット記法で呼び出しできるようにするだけです。
拡張機能は静的に割当てられます、つまり、レシーバ型の仮想の存在ではないということを強く認識してください。これは呼び出される拡張関数が、実行時にその式を評価した結果の型ではなく、関数を呼び出す式の型によって決定されることを意味します。例えば:

open class Shape
​
class Rectangle: Shape()
​
fun Shape.getName() = "Shape" //※Shapeへ拡張関数 getName()を追加
​
fun Rectangle.getName() = "Rectangle" //※Rectangleへ拡張関数 getName()を追加
​
fun printClassName(s: Shape) {
    println(s.getName())
}    
​
printClassName(Rectangle()) 

このコード例では"Shape"と出力されます。なぜなら呼び出される拡張関数は引数sの宣言型(Shapeクラス)にのみ依存するからです。

クラスにメンバ関数があり、同じレシーバ型と関数名を持ち、指定された引数に適用できる拡張関数が定義されている場合、メンバが常に勝ちます。例えば:

class Example {
    fun printFunctionType() { println("Class method") }
}
​
fun Example.printFunctionType() { println("Extension function") }
​
Example().printFunctionType()

このコードは"Class method"を出力します。
※"拡張関数と同じシグニチャ(関数名と引数)を持つメンバ関数がレシーバ型にある場合、常にメンバ関数が呼ばれます。"とか"レシーバ型内で拡張関数と既存のメンバ関数のシグニチャが競合する場合、常にメンバ関数が呼ばれます"という表現じゃ駄目なんだろうか?
Javaのバイトコードにした時に第一引数としてレシーバのオブジェクトを取るらしく、故にkotlin上では同じシグニチャに見えるけれども、実行時のJavaコードでは違うシグニチャになるから同一シグニチャという表現を避けたのかな?
#Kotlin の拡張関数の優先度についてメモ

ただし、拡張関数が同じ名前でシグニチャが異なるメンバ関数をオーバーロードしてもまったく問題ありません。:

class Example {
    fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType(i: Int) { println("Extension function") }

Example().printFunctionType(1)
​

※そもそもシグニチャは関数名も含むはずだから、この文章が何を言いたいのかよくわからない(´・ω・`) "同じ関数名でも引数の数や型が異なる、即ちシグニチャが異なるメンバ関数"という表現の方が妥当なような気がするけど、それでもここでわざわざ言っている意味がわからない。 メンバ関数と同じシグニチャの拡張関数を作成する、即ちオーバーライド的なことをするとメンバ関数が勝つということの対比としてオーバーロードに言及したかったのか?


Null許容レシーバ

拡張機能はnull許容のレシーバ型でも定義できることに注意してください。この場合の拡張関数は、値がnullであるオブジェクト変数であっても呼び出すことができ、本体内で"this == null"を判定できます。この仕組みにより、Kotlinではnull判定をせずにtoString()を呼び出すことができます。:チェックは拡張関数内で行われます。

fun Any?.toString(): String {
    if (this == null) return "null"
    // null判定後、'this'はスマートキャストにより非null型に変換されるので、
    // 以下のtoString()はAnyクラスのメンバ関数のtoString()になります。
    return toString()
}

拡張プロパティ

拡張関数と同様に、Kotlinは拡張プロパティも提供しています。

val <T> List<T>.lastIndex: Int
    get() = size - 1

拡張機能は実際にはメンバ(変数)をクラスに挿入しないため、拡張プロパティにバッキングフィールドを持たせる有効な方法がないことに注意してください。これが拡張プロパティに初期化子(イニシャライザ)が許可されていない理由です。拡張プロパティの動作はゲッター/セッターを明示的に提供することによってのみ定義できます。

例:

val House.number = 1 // エラー:初期化子は拡張プロパティには許可されていません。

※なんでここにエラー例だけしか載せてないんだろうか?


コンパニオンオブジェクト拡張

クラスにコンパニオンオブジェクトが定義されている場合、そのコンパニオンオブジェクトの拡張関数と拡張プロパティを定義することもできます。

コンパニオンオブジェクトの通常のメンバと同じ様に、修飾子としてクラス名のみを使用して呼び出すことができます。: ※クラス名とかって修飾子に分類されるの?

class MyClass {
    companion object { }  // 「コンパニオン」と呼ばれます。
}
​
fun MyClass.Companion.printCompanion() { println("companion") }
​
fun main() {
    MyClass.printCompanion()
}

拡張機能の範囲

ほとんどの場合、拡張機能はトップレベル(パッケージの直下)で定義します。:

package org.example.declarations
 
fun List<String>.getLongestString() { /*...*/}

定義を行ったパッケージの外部でその拡張機能を使用するには、呼び出し側でインポートする必要があります。:
call siteってあるけど、ピンポイントではなく、そのパッケージでインポートをするんだから呼び出し側(side)とかの方が適切ではなかろうか?

package org.example.usage
​
import org.example.declarations.getLongestString
​
fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

詳細については、インポートを参照してください。


拡張関数をメンバとして宣言する

クラス内部で、別のクラスの拡張を宣言できます。このような拡張の内部には複数の暗黙的なレシーバがあります。(これらのオブジェクトのメンバには、修飾子なしでアクセスできます。) 拡張が宣言されているクラスのインスタンスはディスパッチレシーバと呼ばれ、拡張関数のレシーバ型のインスタンスは拡張レシーバと呼ばれます。
※ここのmultipleの意味がよくわからない。暗黙的なレシーバオブジェクトが存在するのは良いとしても。

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}
​
class Connection(val host: Host, val port: Int) {
     fun printPort() { print(port) }
​
     fun Host.printConnectionString() {
         printHostname()   // Host.printHostname()を呼ぶ。
         print(":")
         printPort()   // Connection.printPort()を呼ぶ。
     }
​
     fun connect() {
         /*...*/
         host.printConnectionString()   // 拡張関数 printConnectionString()を呼ぶ。
     }
}
​
fun main() {
    Connection(Host("kotl.in"), 443).connect()   //"kotl.in:443"と出力される。
    //Host("kotl.in").printConnectionString(443)  //Connectionの外側で拡張関数 printConnectionString()は利用出来ないので、この呼出はエラーになる。
 
}

ディスパッチレシーバと拡張レシーバのメンバ間で名前が競合する場合は、拡張レシーバが優先されます。ディスパッチレシーバのメンバーを参照するには、this修飾構文を使用できます。

class Connection {
    fun Host.getConnectionString() {
        toString()         // Host.toString()を呼ぶ
        this@Connection.toString()  // Connection.toString()を呼ぶ
    }
}

メンバとして宣言される拡張関数は、openとして宣言でき、サブクラスでオーバーライドすることができます。つまり、このような関数の割当は、ディスパッチレシーバ型に対しては仮想ですが、拡張レシーバ型に対しては静的です。

※ここもopenとして宣言できると表現するよりも素直に"メンバ関数と同様にopenを用いることによってサブクラスでオーバーライドすることができます。"とかの方が良いような。

open class Base { }
​
class Derived : Base() { }
​
open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }
​
    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }
​
    fun call(b: Base) {
        b.printFunctionInfo()   // call the extension function
    }
}
​
class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }
​
    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}
​
fun main() {
    BaseCaller().call(Base())   // "Base extension function in BaseCaller"
    DerivedCaller().call(Base())  // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
    DerivedCaller().call(Derived())  // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}

可視性に関する注意

拡張機能は、同じスコープで宣言された通常の関数と同じように、他のエンティティの可視性を利用します。例えば:

-ファイルのトップレベルで宣言された拡張機能は、同じファイル内の他のprivateトップレベル宣言にアクセスできます。 :

-拡張機能がそのレシーバ型の外部で宣言されている場合、そのような拡張機能はレシーバのprivateメンバにアクセスできません。

シールクラス(密閉クラス) : kotlin

シールクラス(密閉クラス)

シールクラスは限定的なクラス階層を表すために使用されます。
このクラスでは値は限定されたセットの型の1つを持つことができますが、それ以外の型を持つことが出来ません。

ある意味で、これらは列挙型クラスの拡張です。:
列挙型の値のセットも制限されますが、各列挙型定数は単一のインスタンスとしてのみ存在するのに対し、シールクラスのサブクラスは、状態を含むことができる複数のインスタンスを持てます。

シールクラスを宣言するには、クラス名の前にsealed修飾子を加えます。シールクラスはサブクラスを持つことができますが、それらはすべてシールクラス自体と同じファイルで宣言する必要があります。 (Kotlin 1.1より前は、ルールはさらに厳格でした。クラスは、シールクラスの宣言内にネストする必要がありました)。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

(上記の例では、Kotlin 1.1で追加された新機能を利用しています。
データクラスがシールクラスを含む他のクラスを継承出来るようになりました。)

シールクラス自体は抽象クラスであり、直接インスタンス化は出来ず、抽象メンバを持てません。

シールクラスは、privateではないコンストラクタを持つことはできません。(それらのコンストラクタはデフォルトでprivateです)。

シールクラスのサブクラスを継承するクラス(間接継承)は、必ずしも同じファイル内ではなく、どこにでも配置できることに注意してください。

シールクラスを使用する主な利点は、when式で使用するときに役立ちます。ステートメントがすべてのケースをカバーしていることを確認できる場合は、ステートメントにelse句を追加する必要はありません。ただし、これはwhenをステートメントとしてではなく、式として使用(し、その結果を使用)する場合にのみ機能します。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 全てのケースをカバーしている為、`else`句は必要ありません。
}