#はじめに
@ToraDadyさんが発表を担当されたKotlinスタートブック読書勉強会#2にオーディエンスとして参加してきました。
普段、Swiftを書いているiOSデベロッパーが、Kotlinとの文法の違いをどう意識したか浅めの感想ですが、メモとして書きたいと思いました。
基礎文法がテーマでしたので、その範囲で、KotlinとSwiftの異同について感想を併せて記したいと思います。
※なお、Swiftのコードは完全に独自に自分で書いたものです。Swift3系だとどういう表現に対応するか比較するために添えて、Playgroundで試しつつ書きました。(もしKotlinとの対応などに誤解があれば、教えていだたけると助かります。)
定数リテラル
いきなり定数リテラルの説明ですが、会場での話の流れに沿ってメモした通りに書いていますね。
まず、Kotlinでの定数リテラルについて。
1_234 // == 1234
かつてKotlinでは定数を表現するときに、区切り記号として _ を使えなかったそうです。実際、ガイドにしているKotlinスタートブックでは、「現時点では」使えないと説明されています。
しかし、現在では使えます。例えば、会計で使うカンマのように使えるので、金額の定数を間違えずに記述するときに使えますね。
Swiftでも全く同じ記法ですね。区切りはどういう単位でも構わないようです。
1_234 // == 1234
#厳格な型チェック
ObjcからSwiftに移行したときに型チェックの厳しさに驚きました。Kotlinも同じように厳格です。
大は小を兼ねるの関係から、Long型の変数にIntの数値を代入できそうですが、型が違うのでエラーが発生します。
val foo = 123 //Int
val bar:Long = foo //エラー
Long型はSwiftにそもそもありませんが、Int16などが整数型として用意されています。同様の操作をするとエラーが発生します。
let foo = 123 //Int
let bar:Int16 = foo //エラー
//let bar:Int16 = Int(foo) //キャストすれば代入可能。
#変数の文字列展開
変数を文字列の中で展開したい場合、kotlinでは次のような書式を取ります。
val name = "Jack"
"Hello, ${name}!"
//または、型推論が機能する場合は{}を省略しても記述できる。
//"Hello, $name!"
let name = "Jack"
"Hello, \(name)"
#トリプルクオート
文字列を扱う " " だと、改行コードを表現する場合に改行コードを明示的に記述する必要があり、また一行で記述するのでレイアウトが崩れて見づらくなります。
"""でくくると、改行コードなしで改行した文字列を指定できます。
全く同じ機能がSwiftにも、Swift4から導入されましたね。
会場の声: CUIのヘルプのテキストをソースコードに書くときに'''は便利に使える。
#ヌルの配列
Int型でヌルになっている3要素の配列を作るには?
val ints = arrayOfNulls<Int>(3)
Swiftでは、次のような基本になるので、kotlinのほうが簡潔な印象ですね。
let strs = Array<Int?>(repeatElement(nil, count: 3))
しかし、現在Kotlinには配列リテラルがないので、Swiftでは次のように書けるものがKotlinでは上のようにしか書けません。
let strs: Array<Int?> = [nil,nil,nil]
会場でのやりとり: Pythonのようにstrs[-1]と記述できるか? →できない。範囲外指定エラーが発生します。Swiftと同じですね。(ちなみに、Pythonでは末尾の要素への参照を意味するそうです。)
#配列リテラルがない
Swiftでは[]で書けますが、Kotlinでは現時点ではarrayOf()とするしかありません。
let strs = ["Abc","Def"]
var strs = arrayOf("Abc","Def")
会場でのやりとり: なぜKotlinでは配列リテラルを用意していないのか?
型を混合した配列の扱い
var arr = arrayOf[1,"2"] //arrはAny型として推論される
arr[0] + arr[1] を実行すると、Any型に+の演算子が定義されていないので、エラーが発生します。
Swiftでも同様のコードをビルドすると、エラーが発生するという点では同じです。しかし、Swiftの場合、型推論の時点でエラーが生じます。(Heterogeneous collection literal could only be inferred to '[Any]') KotlinがAnyの配列と理解した上で、演算子の未定義でエラーを生じることと違いますね。
let arr:Array<Any> = [1,"2"] //これならok
#ArrayとListの違い
Arrayと違って、Listはインターフェースである。また、Listには複数実装がある。例えば、LinkedListなど。
会場の声: どういう場合に使い分けるのか? →Listのバリエーションを便利に使えるときはList系が便利。
ここにいうインターフェースは、Swiftのプロトコルの概念に近い?
Map
Swiftのmap関数のようなものではありません。むしろDictionaryに対応するものです。
val themap = mutableMapOf("one" to 1, "two" to 2)
toで対応させるのが新鮮ですよね。MapOf()とmutableMapOf()の違いは、可変性の有無です。
会場でプレゼンターの@ToraDadyさんが実演してくださった要素の追加の記法が新鮮でした。
themap += "three" to 3 //themap == ("one" to 1, "two" to 2, "three" to 3)
機能的にはSwiftのDictionaryと同じですね。
let dict = ["one":1, "two":2, "three":3]
レンジ
Kotlinでは、範囲を扱うためのRange系のオブジェクトが用意されています。IntRange、LongRange、CharRangeです。
関連: Ranges
1から10までの範囲を記述するには、
1..10
と書けます。
Swiftだと閉区間は、もう一つピリオドが多いですよね。for inでよく見るやつです。
1...10
Kotlinでは、1から10未満の場合は次のように書きます。
1 until 10
Swiftのほうが分かりやすいと思うのは慣れのせいでしょうか。
1..<10
会場の声: Kotlin方式だと英文のように読みやすい面がある。kotlinでは 10 in 1 until 10で式として評価できるが、Swiftではできない。
リストと反転
kotlinではリスト化とその反転のための関数があります。
次のようにすると、(5,4,3,2,1)というリストを取得できます。
(1..5).reversed().toList() //(1)
(1..5).toList().reversed() //(2)
(1)と(2)では最終の値としては同じ結果が得られます。しかし、(2)はリストを2度作ることになるので、比較的にパフォーマンスが悪いです。そういう会場での議論がありました。
条件分岐: if
kotlinのifは式として使えるのが新鮮です。どういう意味かというと、例えば、次のように記述できるのです。
val message = if(flag == true) "yes" else "no"
同じことをSwiftでしようとしたら、三項演算子を使うことになります。ほぼ同じですし、簡潔に記述できるのでこちらのほうが良いのではないかという感想があります。ただ、Kotlinのif文のほうが一見して分かりやすいですよね。
let message = flag ? "yes" : "no"
when
kotlinのwhenというのは見慣れないキーワードで驚きました。実質的には、Swiftのswitchのように使えるものだと理解しました。
Swiftで次のようなswitch文を書けますが、同様のものがwhenだと以下のようになります。
switch x {
case 1:
"one"
case 2:
"two"
default:
"unknown"
}
when (x){
1 -> "one"
2 -> "two"
else -> { "unknown" }
}
caseという文字列がないので見た目が簡潔ですね。
elseはdefaultに対応するものです。原則として必須です。
会場の声: elseを省略できるときはどんな場合か?
結論的には、sealedClassと列挙子Enumの場合は、全件を挙げておくと、例外的にelseを省略できる、ということのようです。反対に言えば、それ以外の場合に論理的にelseがありえない場合も、ビルドするためにはelseが不可欠ということですね。
ところで、型を評価することも可能です。
when (x){
is Int -> "Int"
else -> { "unknown" }
}
同様のことをSwiftで書くと、
switch x {
case is Int:
"Int"
default:
"unknown"
}
挙動での違いとしては、Kotlinでは、型で分岐すると、その型でキャストされる機能が働くということでした。こういうコードが動きます。
var x:Any = "123"
when (x){
is String -> Int(x) //この中に入ると、xはAnyではなくStrint型にキャストされる
else -> { "unknown" }
}
Swiftで真似しようとすると、キャストされないのでエラーが発生しますね。
let x:Any = "123"
switch x {
case is String:
Int(x) //xはAnyなのでエラーになる。
//Int(String(x)) //これは可。
default:
"unknown"
}
追記: case let asを使えば、分岐しつつキャストする表現ができるという件、@sora0077@githubさんにご指摘いただきました。次のように書けば、kotlinの例のように動きますね。
let x:Any = "123"
switch x {
case let x as String: //分岐してキャストできる
Int(x)
default:
"unknown"
}
この他にkotlinでもwhileがあります。
for
kotlinでは、iteratableなクラスを自前で実装できます。SwiftでもIteratableプロトコルを採用すれば、同様の実装ができます。
会場の声: kotlinでは、Iteratorからの継承関係を記述しなくても、operator fun next:(): Tと operator fun hasNext()] Boolean を簡潔に定義するだけで、インテレートできるクラスを実装できる。このことに驚いた、とのお話がありました。
Swiftにはないbreak loopという機能があります。(追記: @lovee さんのご指摘で、同等の機能がSwiftにもあることを知りました。外側のループにラベルをつけて、break ラベル、とすると、外側にブレイクできるというものです。コードはコメント欄をご覧ください。)
loop@ for (x in 1..2){
for (y in 1..10){
if (y==5)
break@loop
}
}
//次の処理
break@loopとすると、loop@へ抜ける。上の例では、xが1で、yが1ないし5と来て、次の処理に降りていく流れになります。2回目は回りません。
コードをあっさり記述できる面もありますが、ジャンプすると処理を追いにくくなる面も出てくると思うので、使い方が大事ですね。
#おわりに
別の言語との比較をすると、いつも使っている言語の機能を確認するきっかけを得られたり、将来の仕様変更への想像力が少しつく気がしました。
私は第一回も参加させてもらいましたが、いずれもKotlinのコードを丁寧に、頭からおさらいしてくださるので大変勉強になります。実際に実務でkotlinのコードを書かれているエンジニアの方がお話をインタラクティブに展開してくださると、本の記述の重み付けができてよいですね。
最後に、今回スピーカーを担当された@ToraDadyさん、主催者の皆さま、今回会場提供してくださったOrigamiさん、会場で議論を聞かせていただいた参加者のみなさまありがとうございました。
#関連
読書勉強会という形で、テキストとしてこの本が採用されています。
Kotlinスタートブック