上記の理由から、近年、このようなユーティリティを作成する必要があるときはいつでも、Goを使用しました。
Goの構文は比較的単純で、標準ライブラリとして優れており、ガベージコレクションがあり、出力では1つのバイナリが取得されます。 他に何が必要なのでしょうか?
少し前に、KotlinはKotlin Nativeの形で同様の分野に挑戦し始めました。 提案は有望に思えました-GC、単一のバイナリ、使い慣れた便利な構文。 しかし、すべてが私たちが望むほど良いですか?
私たちが解決しなければならない問題:Kotlin Nativeで簡単なファイルウォッチャーを作成します。 引数として、ユーティリティはファイルへのパスとスキャンの頻度を受け取る必要があります。 ファイルが変更された場合、ユーティリティは同じフォルダに新しい名前でそのコピーを作成する必要があります。
つまり、アルゴリズムは次のようになります。
fileToWatch = getFileToWatch() howOftenToCheck = getHowOftenToCheck() while (!stopped) { if (hasChanged(fileToWatch)) { copyAside(fileToWatch) } sleep(howOftenToCheck) }
さて、達成したいことは整理されているようです。 コードを書く時間。
水曜日
最初に必要なのはIDEです。 Vimを愛する人は心配しないでください。
おなじみのIntelliJ IDEAを起動し、Kotlin Nativeでは単語からはまったくできないことがわかりました。 CLionを使用する必要があります。
2004年にCに最後に遭遇した人の不幸はまだ終わっていません。 ツールチェーンが必要です。 OSXを使用する場合、ほとんどの場合、CLionは適切なツールチェーン自体を見つけます。 しかし、Windowsを使用することに決め、Cでプログラミングしない場合は、 Cygwinをインストールするためにチュートリアルをいじる必要があります 。
IDEがインストールされ、ツールチェーンが整理されました。 すでにコードを書き始めることができますか? ほぼ。
Kotlin Nativeはまだ実験段階なので、CLionのプラグインはデフォルトではインストールされません。 そのため、大事な碑文「New Kotlin / Native Application」を見る前に、 手動でインストールする必要があります。
いくつかの設定
そして、ようやく空のKotlin Nativeプロジェクトができました。 興味深いことに、MakefileではなくGradleに基づいており、Kotlin Scriptバージョンにも基づいています。
build.gradle.kts
見てください:
plugins { id("org.jetbrains.kotlin.konan") version("0.8.2") } konanArtifacts { program("file_watcher") }
使用する唯一のプラグインはKonanと呼ばれます。 彼はバイナリファイルを作成します。
konanArtifacts
、実行可能ファイルの名前を指定します。 この例では、
file_watcher.kexe
を取得し
file_watcher.kexe
コード
すでにコードを表示する時間です。 ところで、ここにあります:
fun main(args: Array<String>) { if (args.size != 2) { return println("Usage: file_watcher.kexe <path> <interval>") } val file = File(args[0]) val interval = args[1].toIntOrNull() ?: 0 require(file.exists()) { "No such file: $file" } require(interval > 0) { "Interval must be positive" } while (true) { // We should do something here } }
通常、コマンドラインユーティリティにはオプションの引数とそのデフォルト値もあります。 ただし、たとえば、
path
と
interval
2つの引数が常にあると仮定し
interval
すでにKotlinを使用したことがある人にとっては、
java.io.File
を使用せずに
path
が独自の
File
クラスでラップするのは奇妙に思えるかもしれません。 これについての説明は1〜2分です。
Kotlinのrequire()関数に慣れていない場合、これは引数を検証するためのより便利な方法です。 Kotlin-利便性がすべてです。 次のように書くことができます:
if (interval <= 0) { println("Interval must be positive") return }
一般に、ここに通常のKotlinコードがありますが、興味深いものはありません。 しかし、これからは楽しくなります。
通常のKotlinコードを作成してみましょうが、Javaの何かを使用する必要があるたびに、「おっと!」と言います。 準備はいい?
while
に戻って、すべての
interval
、たとえばピリオドなどのシンボルを刻印しましょう。
var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") // ... Thread.sleep(interval * 1000) }
Thread
はJavaのクラスです。 Kotlin NativeではJavaクラスを使用できません。 Kotlinのクラスのみ。 Javaはありません。
ところで、
main
に
java.io.File
使用しなかったため
さて、それでは何を使用できますか? Cの関数!
var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") sleep(interval) }
ワールドCへようこそ
何が待っているかがわかったので、
File
から
exists()
関数がどのように見えるか見てみましょう。
data class File(private val filename: String) { fun exists(): Boolean { return access(filename, F_OK) != -1 } // More functions... }
File
は単純な
data class
であり、このボックスから
toString()
実装を提供します。これは後で使用します。
「フードの下」では、
access()
関数Cを呼び出します。この関数は、そのようなファイルが存在しない場合に
-1
を返します。
リストのさらに下には、
modified()
関数があります。
fun modified(): Long = memScoped { val result = alloc<stat>() stat(filename, result.ptr) result.st_mtimespec.tv_sec }
型推論を使用して関数を少し単純化することもできますが、ここではこれを行わないことにしました。そのため、関数が
Boolean
などを返さないことは明らかです。
この関数には2つの興味深い詳細があります。 まず、
alloc()
を使用します。 Cを使用しているため、構造を強調表示する必要がある場合があり、これはmalloc()を使用してCで手動で行われます。
これらの構造も手動でリリースする必要があります。 Kotlin Nativeの
memScoped()
関数が
memScoped()
に来てくれます。
最も重要な機能である
opyAside()
を検討することは、私たちに残っています。
fun copyAside(): String { val state = copyfile_state_alloc() val copied = generateFilename() if (copyfile(filename, copied, state, COPYFILE_DATA) < 0) { println("Unable to copy file $filename -> $copied") } copyfile_state_free(state) return copied }
ここでは、
copyfile_state_alloc()
に必要な構造を選択するC関数
copyfile_state_alloc()
を使用し
copyfile()
。
しかし、この構造を自分でリリースする必要があります-を使用して
copyfile_state_free(state)
最後に示すのは、名前の生成です。 ほんの少しのコトリンがあります:
private var count = 0 private val extension = filename.substringAfterLast(".") private fun generateFilename() = filename.replace(extension, "${++count}.$extension")
これは多くのケースを無視するかなり単純なコードですが、例としては役立ちます。
開始する
今、それをすべて実行する方法は?
もちろん、1つのオプションはCLionを使用することです。 彼は私たちのためにすべてをします。
しかし、プロセスをよりよく理解するために、代わりにコマンドラインを使用しましょう。 はい、一部のCIはCLionからコードを実行しません。
./gradlew build && ./build/konan/bin/macos_x64/file_watcher.kexe ./README.md 1
まず、Gradleを使用してプロジェクトをコンパイルします。 すべてがうまくいった場合、次のメッセージが表示されます。
BUILD SUCCESSFUL in 16s
16秒?! はい。JVMのGoやKotlinに比べても、結果は期待はずれです。 そして、これはまだ小さなプロジェクトです。
画面上に点が表示されます。 また、ファイルの内容を変更すると、メッセージが表示されます。 この写真のようなもの:
................................ File copied: ./README.1.md ................... File copied: ./README.2.md
起動時間の測定は困難です。 しかし、たとえばActivity Monitor:852KBを使用して、プロセスに必要なメモリ量を確認できます。 悪くない!
いくつかの結論
そして、Kotlin Nativeの助けを借りて、同じGoのメモリフットプリントよりも小さいメモリフットプリントを持つ単一の実行可能ファイルを取得できることがわかりました。 勝利 そうでもない。
すべてをテストする方法は? GoまたはKotlin'omで働いていた人は、両方の言語でこの重要なタスクに適したソリューションがあることを知っています。 Kotlin Nativeは、これまでのところ悪い結果を出しています。
2017年にJetBrainsはこれを解決しようとしたようです。 しかし、 公式のKotlin Nativeの例でさえテストが行われていないことを考慮すると、明らかにあまり成功していません。
別の問題は、クロスプラットフォーム開発です。 私よりも大きなCで作業した人は、私の例がOSXでは動作するがWindowsでは動作しないことにすでに気付いているでしょう。なぜなら、
platform.darwin
のみ利用可能ないくつかの関数に依存しているから
platform.darwin
。 将来的には、Kotlin Nativeが、たとえばファイルシステムを操作するときにプラットフォームから抽象化できるラッパーが増えることを願っています。
ここですべてのコード例を見つけることができます。
英語で読みたい場合は、元の記事へのリンク