Home About
Kotlin , Kotlin Script , Groovy

Kotlin をコマンドラインで使う ―「書き捨てのプログラム」を Kotlin と Groovy で書く―

Kotlin をコマンドラインで使う方法は、 以前のエントリー Kotlin Script が便利 Javaのライブラリも使える でも書いたのですが、 何通りかの方法があるので、整理しました。 Kotlin でライブラリ(jar)をつくり、それを Groovy スクリプトから使うこともできます。

kotlinc コマンドを使えるようにする

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)

方法1) 実行可能 jar を生成して実行する

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 するだけで実行できます。

方法2) ライブラリとメイン関数を別ファイルにする

例えば、リストの合計を計算する 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

方法3) ライブラリは jar を生成しておき 主な処理は main.kts ファイルに書く

または、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 を使いたいところです。 そこで、複雑な部分だけを 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 から Kotlin で生成したライブラリを使う

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 で 関数リテラルを使った場合

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 を利用するのはそんなに楽ではなかった。

Liked some of this entry? Buy me a coffee, please.