Saat Anda menulis utilitas baris perintah, hal terakhir yang ingin Anda andalkan adalah bahwa JVM, Ruby atau Python diinstal pada komputer di mana ia akan berjalan. Saya juga ingin memiliki satu file biner yang mudah diluncurkan. Dan jangan terlalu repot dengan manajemen memori.
Untuk alasan di atas, dalam beberapa tahun terakhir, setiap kali saya perlu menulis utilitas seperti itu, saya menggunakan Go.
Go memiliki sintaksis yang relatif sederhana, pustaka standar yang baik, ada pengumpulan sampah, dan pada output kita mendapatkan satu biner. Tampaknya apa lagi yang dibutuhkan?
Belum lama berselang, Kotlin juga mulai mencoba sendiri di bidang serupa dalam bentuk Kotlin Asli. Proposal itu terdengar menjanjikan - GC, sebuah sintaks biner tunggal, akrab dan nyaman. Tapi apakah semuanya sebagus yang kita inginkan?
Masalah yang harus kita pecahkan: menulis pengamat file sederhana di Kotlin Native. Sebagai argumen, utilitas harus mendapatkan jalur file dan frekuensi pemindaian. Jika file telah berubah, utilitas harus membuat salinannya di folder yang sama dengan nama baru.
Dengan kata lain, algoritma akan terlihat seperti ini:
fileToWatch = getFileToWatch() howOftenToCheck = getHowOftenToCheck() while (!stopped) { if (hasChanged(fileToWatch)) { copyAside(fileToWatch) } sleep(howOftenToCheck) }
Oke, jadi apa yang ingin kita capai tampaknya beres. Saatnya menulis kode.
Rabu
Hal pertama yang kita butuhkan adalah IDE. Pecinta Vim jangan khawatir.
Kami meluncurkan IntelliJ IDEA yang familier dan menemukan bahwa di Kotlin Native tidak bisa dari kata sama sekali. Anda harus menggunakan
CLion .
Kesalahpahaman orang yang terakhir kali bertemu C pada tahun 2004 belum berakhir. Butuh rantai alat. Jika Anda menggunakan OSX, kemungkinan besar CLion akan menemukan rantai alat yang sesuai itu sendiri. Tetapi jika Anda memutuskan untuk menggunakan Windows dan tidak memprogram di C, Anda harus bermain-main dengan tutorial untuk menginstal
Cygwin .
IDE dipasang, disortir dari toolchain. Bisakah saya sudah mulai menulis kode? Hampir.
Karena Kotlin Native masih agak eksperimental, plugin untuk itu di CLion tidak diinstal secara default. Jadi sebelum kita melihat prasasti berharga "New Kotlin / Aplikasi Asli" harus
menginstalnya secara manual .
Beberapa pengaturan
Jadi, akhirnya kami memiliki proyek asli Kotlin kosong. Menariknya, ini didasarkan pada Gradle (dan bukan pada Makefiles), dan bahkan pada versi Script Kotlin.
build.gradle.kts
:
plugins { id("org.jetbrains.kotlin.konan") version("0.8.2") } konanArtifacts { program("file_watcher") }
Satu-satunya plugin yang akan kita gunakan disebut Konan. Dia akan menghasilkan file biner kita.
Dalam
konanArtifacts
kami menentukan nama file yang dapat dieksekusi. Dalam contoh ini, kita mendapatkan
file_watcher.kexe
Kode
Sudah waktunya untuk menunjukkan kodenya. Ngomong-ngomong, ini dia:
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) {
Biasanya, utilitas baris perintah juga memiliki argumen opsional dan nilai defaultnya. Tetapi sebagai contoh, kita akan mengasumsikan bahwa selalu ada dua argumen:
path
dan
interval
Bagi mereka yang sudah bekerja dengan Kotlin, mungkin aneh jika
path
membungkus di kelas
File
sendiri, tanpa menggunakan
java.io.File
. Penjelasan untuk ini adalah satu atau dua menit.
Jika Anda tidak terbiasa dengan fungsi require () di Kotlin, ini hanya cara yang lebih mudah untuk memvalidasi argumen. Kotlin - ini semua tentang kenyamanan. Orang bisa menulis seperti ini:
if (interval <= 0) { println("Interval must be positive") return }
Secara umum, ini kode Kotlin yang biasa, tidak ada yang menarik. Tapi mulai sekarang akan menyenangkan.
Mari kita coba menulis kode Kotlin biasa, tetapi setiap kali kita perlu menggunakan sesuatu dari Jawa, kita mengatakan "oops!". Apakah kamu siap
Mari kita kembali ke
while
kita, dan biarkan ia mencetak setiap
interval
beberapa simbol, misalnya, suatu periode.
var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".")
Thread
adalah kelas dari Jawa. Kami tidak dapat menggunakan kelas Java di Kotlin Native. Hanya kelas Kotlin. Tidak ada java
Omong-omong, karena pada
main
kita tidak menggunakan
java.io.File
Nah, lalu apa yang bisa digunakan? Fungsi dari C!
var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") sleep(interval) }
Selamat datang di dunia C
Sekarang kita tahu apa yang menanti kita, mari kita lihat seperti apa fungsi yang
exists()
dari
File
kita:
data class File(private val filename: String) { fun exists(): Boolean { return access(filename, F_OK) != -1 }
File
adalah
data class
sederhana, yang memberi kita implementasi
toString()
dari kotak, yang akan kita gunakan nanti.
"Di bawah tenda," kami memanggil fungsi
access()
C, yang mengembalikan
-1
jika file seperti itu tidak ada.
Lebih jauh ke bawah daftar kami memiliki fungsi yang
modified()
:
fun modified(): Long = memScoped { val result = alloc<stat>() stat(filename, result.ptr) result.st_mtimespec.tv_sec }
Fungsi dapat disederhanakan sedikit menggunakan inferensi tipe, tetapi di sini saya memutuskan untuk tidak melakukan ini, sehingga jelas bahwa fungsi tidak kembali, misalnya,
Boolean
.
Ada dua detail menarik dalam fungsi ini. Pertama, kami menggunakan
alloc()
. Karena kita menggunakan C, kadang-kadang kita perlu mengalokasikan struktur, dan ini dilakukan dalam C secara manual, menggunakan malloc ().
Struktur ini juga harus dirilis secara manual. Fungsi
memScoped()
dari Kotlin Native datang untuk
memScoped()
, yang akan melakukannya untuk kita.
Tetap bagi kami untuk mempertimbangkan fungsi yang paling berat:
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 }
Di sini kita menggunakan fungsi C
copyfile_state_alloc()
, yang memilih struktur yang diperlukan untuk
copyfile()
.
Tetapi kita harus melepaskan struktur ini sendiri - menggunakan
copyfile_state_free(state)
Hal terakhir yang tersisa untuk ditampilkan adalah generasi nama. Hanya ada sedikit Kotlin:
private var count = 0 private val extension = filename.substringAfterLast(".") private fun generateFilename() = filename.replace(extension, "${++count}.$extension")
Ini adalah kode yang cukup naif yang mengabaikan banyak kasus, tetapi akan menjadi contoh.
Mulai
Sekarang bagaimana menjalankannya semua?
Satu opsi tentu saja menggunakan CLion. Dia akan melakukan segalanya untuk kita.
Tapi mari kita gunakan baris perintah saja untuk lebih memahami prosesnya. Ya, dan beberapa CI tidak akan menjalankan kode kami dari CLion.
./gradlew build && ./build/konan/bin/macos_x64/file_watcher.kexe ./README.md 1
Pertama-tama, kami mengkompilasi proyek kami menggunakan Gradle. Jika semuanya berjalan dengan baik, pesan berikut akan muncul:
BUILD SUCCESSFUL in 16s
Enam belas detik ?! Ya, dibandingkan dengan Go atau bahkan Kotlin untuk JVM, hasilnya mengecewakan. Dan ini masih proyek kecil.
Sekarang Anda akan melihat titik-titik berjalan di layar. Dan jika Anda mengubah isi file, sebuah pesan akan muncul. Sesuatu seperti gambar ini:
................................ File copied: ./README.1.md ................... File copied: ./README.2.md
Waktu peluncuran sulit diukur. Tetapi kita dapat memeriksa berapa banyak memori yang dibutuhkan oleh proses kami, menggunakan, misalnya, Activity Monitor: 852KB. Tidak buruk!
Beberapa kesimpulan
Jadi, kami menemukan bahwa dengan bantuan Kotlin Native, kami dapat memperoleh satu file yang dapat dieksekusi dengan jejak memori kurang dari Go yang sama. Kemenangan Tidak juga.
Bagaimana cara menguji semuanya? Mereka yang bekerja dengan Go atau Kotlin'om tahu bahwa dalam kedua bahasa ada solusi yang baik untuk tugas penting ini. Sejauh ini Kotlin Native memiliki kesepakatan buruk.
Tampaknya di
JetBrains 2017 mencoba menyelesaikan ini . Tetapi mengingat bahwa bahkan
contoh asli Kotlin resmi tidak memiliki tes, tampaknya belum terlalu berhasil.
Masalah lain adalah pengembangan lintas platform. Mereka yang bekerja dengan C lebih besar dari saya mungkin sudah memperhatikan bahwa contoh saya akan bekerja pada OSX, tetapi tidak pada Windows, karena saya mengandalkan beberapa fungsi yang hanya tersedia dengan
platform.darwin
. Saya berharap bahwa di masa depan Kotlin Native akan memiliki lebih banyak pembungkus yang akan memungkinkannya untuk abstrak dari platform, misalnya, ketika bekerja dengan sistem file.
Anda dapat
menemukan semua contoh kode di
sini.Dan
tautan ke artikel asli saya , jika Anda lebih suka membaca dalam bahasa Inggris