Kotlin をコマンドラインで使う方法は、 以前のエントリー Kotlin Script が便利 Javaのライブラリも使える でも書いたのですが、 何通りかの方法があるので、整理しました。 Kotlin でライブラリ(jar)をつくり、それを Groovy スクリプトから使うこともできます。
macOS Monterey で JavaVM が入っている状態で作動確認しています。 ただ、JavaVM があれば、Windows/Linux でも同じように使えると思います。
$ java --version
openjdk 17.0.3 2022-04-19 LTS
OpenJDK Runtime Environment Zulu17.34+19-CA (build 17.0.3+7-LTS)
OpenJDK 64-Bit Server VM Zulu17.34+19-CA (build 17.0.3+7-LTS, mixed mode, sharing)
kotlinc コマンドなどを一連のコマンドをコマンドラインから使えるようにするには、このページ https://kotlinlang.org/docs/command-line.html の説明のとおりです。 現時点での最新版は Github の v1.7.0 のリリースページ から kotlin-compiler-1.7.0.zip をダウンロードして適当なディレクトリに展開してパスを通すだけです。
これで kotlinc コマンドが使えるようになりました。
$ kotlinc -version
info: kotlinc-jvm 1.7.0 (JRE 17.0.3+7-LTS)
kotlinc はコンパイラでソースから jar を生成します。
たとえば、main.kt を以下のように書いたとして:
fun main(){
println("Hello, World!")
}
このコードは 以下のコマンドで main.jar を生成できます。このように:
$ kotlinc main.kt -include-runtime -d main.jar
実行するには:
$ java -jar main.jar
です。
この手順を Makefile に書いておきます。
run: main.jar
java -jar $<
main.jar: main.kt
kotlinc $< -include-runtime -d $@
これで main.kt を書き直したら make するだけで実行できます。
例えば、リストの合計を計算する total 関数をライブラリとして扱うファイル library.kt に作成します。
fun total(list: List<Int>): Int {
val f: (Int, Int)-> Int = { acc, element->
acc + element
}
return list.fold(0, f)
}
そして、この関数を使う main 関数を main.kt に書きます。
fun main(){
println( total( listOf(1,2,3,4,5) ) )
}
library.kt と main.kt から 実行可能な main.jar を生成して、実行します。
$ kotlinc library.kt main.kt -include-runtime -d main.jar
$ java -jar main.jar
15
または、library.kt のみを library.jar としてビルドしておき、それを main.kts から使う方法もあります。
まず library.jar を生成します。
$ kotlinc library.kt -d library.jar
次にこれを使う main.kts を書きます。
println( total( listOf(1,2,3,4,5) ) )
library.jar をクラスパスに指定して、main.kts を実行します。
$ kotlinc -cp library.jar -script main.kts
15
実験コードを Kotlin でコマンドラインで書く場合はこの方法が一番便利な気がする。
Makefile を作成しておきます。
run: library.jar main.kts
kotlinc -cp $< -script main.kts
library.jar: library.kt
kotlinc $< -d $@
clean:
$(RM) library.jar
型を指定できるなど Kotlin は複雑なことを記述するには有用ですが、簡単な処理を記述するには少し冗長です。 簡単な処理をする部分には Groovy を使いたいところです。 そこで、複雑な部分だけを Kotlin で記述をしてそれをライブラリとしてコンパイル、 Groovy からそれを使うことを考えます。
ライブラリを生成する方法はここに https://kotlinlang.org/docs/command-line.html#compile-a-library 説明があります。
$ kotlinc hello.kt -d hello.jar
とすればよいと書いてあります。 しかし、この方法で生成した hello.jar を Groovy から使おうとすると、以下のエラーが出ます。
$ groovy -cp hello/hello.jar main
Caught: java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
kotlin/jvm/internal/Intrinsics というクラスがない! ならば、さきほど実行可能jarを生成したのと同じように -include-runtime オプションをつければいいんじゃないの?
$ kotlinc hello.kt -include-runtime -d hello.jar
こうして生成した hello.jar ライブラリはエラーなしで Groovy から実行できました。 以下に詳しく書きます。
Groovy の実行環境はすでにインストール済みとして話を進めます。
この環境でのGroovy Version:
$ groovy -version Groovy Version: 4.0.3 JVM: 17.0.3 Vendor: Azul Systems, Inc. OS: Mac OS X
まず定番の挨拶するだけの Hello クラスを hello.kt に記述します。
class Hello {
fun greeting(name: String): String {
return "Hello, ${name}!"
}
}
kotlinc で hello.jar ライブラリを生成:
$ kotlinc hello.kt -include-runtime -d hello.jar
main.groovy にこの Hello クラスを使う Groovy スクリプトを書きます。
println new Hello().greeting('World')
そのまま実行するともちろん失敗します。
$ groovy main
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/path/to/main.groovy: 1: unable to resolve class Hello
@ line 1, column 9.
println new Hello().greeting('World')
-cp でクラスパスに hello.jar ライブラリを指定して実行します。
$ groovy -cp hello.jar main
Hello, World!
できた!簡単ですね。
あとは、これを Makefile にまとめておきましょう。
run: hello.jar main.groovy
groovy -cp $< main
hello.jar: hello.kt
kotlinc $< -include-runtime -d $@
clean:
$(RM) hello.jar
これで hello.kt か main.groovy を修正したら make して実行作動確認のイテレーション開発がコマンドラインでできるようになりました。
Kotlin では greeting メソッドは次のように書き換えることができます。
class Hello {
/*
fun greeting(name: String): String {
return "Hello, ${name}!"
}
*/
val greeting: (String)->String = { name->
"Hello, ${name}!"
}
}
先ほど同様に hello.jar を生成します。
$ kotlinc hello.kt -include-runtime -d hello.jar
これを Groovy から使うとどうなるでしょうか?
main.groovy を以下のように記述します。
println new Hello().greeting
実行します。
$ groovy -cp hello.jar main
(kotlin.String) -> kotlin.String
kotlin.String を受け取り kotlin.String を返す関数オブジェクトが出力されたようです。
では World 文字列を適用してみます。
println new Hello().greeting('World')
これを実行してみます。
$ groovy -cp hello.jar main.groovy
Caught: groovy.lang.MissingMethodException: No signature of method: Hello.greeting() is applicable for argument types: (String) values: [World]
うまくいきません。
調べてみると・・・ この kotlin.String を受け取り kotlin.String を返す関数オブジェクト は kotlin.jvm.functions.Function1<String, String> というオブジェクトであることが判明。 つまり、次のようにすると groovy からこの関数オブジェクトを使用できます。
import kotlin.jvm.functions.Function1
Function1<String, String> getGreetingFunction = new Hello().getGreeting()
println getGreetingFunction.invoke('World')
今はわかりやすく型を書きましたが、通常は以下のように書くでしょう。
def getGreetingFunction = new Hello().getGreeting()
println getGreetingFunction.invoke('World')
これで、kotlin の関数オブジェクトも Groovy から使えなくはない。 Groovy からのみ使うことを想定して kotlin ライブラリを書くのであれば明らかに使うべきではない手法ですが。
ちなみに、Function1 の 1 は 引数が一つだから 1 なのだそうです。 Function0 から Function22 まで用意されているだとか。
<String, String>
の部分は String をうけとり、String を返す関数という意味。 もし、何も値を返さない関数なら(気持ち悪いけど)Function1<String,Unit>
のようになる。 Unit は kotlin.Unit です。本 Kotlin プログラミング The Big Nerd Ranch Guideの Java との相互運用のセクションに全部書いてありました。ありがとう!
Groovy から Kotlin を利用するのはそんなに楽ではなかった。