アンモナイトスクリプティング
1. 序章
この記事では、アンモナイトスクリプティングに焦点を当てます。 特に、Ammoniteを REPL (Read-Eval-Print Loop)として使用する方法や、sbtとの統合ではなく、Scalaスクリプトを記述して実行する方法に焦点を当てます。
まず、アンモナイトとは何かを見ていきます。 次に、スクリプトの詳細を掘り下げて、sbtベースのScalaプロジェクトを作成する負担なしにAmmoniteスクリプトを作成して実行する方法を確認します。
2. アンモナイトとは何ですか?
アンモナイトはそうするためのいくつかのツールを提供します:
- Scala REPL、構文の強調表示や複数行の編集など、デフォルトのScalaREPLに関していくつかの改善があります
- Scalaコードを実行できるScalaランタイム
- 複雑なBashスクリプトの必要性を回避するために、構文がScalaに基づいているシステムシェル
- 簡潔な方法でファイルシステム操作を処理するためのAmmonite-Opsという名前のScalaライブラリ
このチュートリアルでは、Scalaスクリプトに焦点を当てます。
3. アンモナイトスクリプトの作成と実行
このセクションでは、Ammoniteスクリプトがどのように機能するかを見ていきます。 いくつかの基本的なスクリプトから始めて、次にもっと複雑なスクリプトに移ります。
3.1. 私たちの最初のスクリプト
通常、Scalaコードを実行する場合(たとえば、新しいライブラリを試す場合)、新しいプロジェクトを作成し、ビルドファイルを設定し、プラグインをインポートする必要があります。 ただし、特に実験したいだけの場合は、本格的なプロジェクトに多くの冗長なコードが含まれることがあります。 Ammoniteスクリプトは、コマンドラインから直接実行できるファイルを作成できるようにすることで、このような負担を軽減することを目的としています。 最も単純なケースでは、そのようなスクリプトは単に一連のステートメントです。
例を見てみましょう:
case class Coordinates(x: Int, y: Int) {
def moveX(offset: Int): Coordinates = Coordinates(x + offset, y)
def moveY(offset: Int): Coordinates = Coordinates(x, y + offset)
}
val point = Coordinates(10, 15)
println(point.x)
val movedPoint = point.moveX(7)
println(movedPoint.x)
このスクリプトは、新しいケースクラス、座標を定義し、それを使用して新しい値ポイントをインスタンス化するだけです。 標準のScalaコードを書いているかのように、 Coordinates でメソッドを定義し、それらをプログラムで使用できます。 これを行うには、 pointでmoveX()を呼び出します。これにより、Coordinatesの新しいインスタンスが返されます。
スクリプトを実行するには、スクリプトをSimpleScript.scなどの。scファイルとして保存し、 ammSimpleScript.scで実行します。 ]。 Ammoniteは、最初に実行されたときにスクリプトをコンパイルします。 それを実行してみましょう:
$ amm SimpleScript.sc
10
17
上記の例は、標準のScalaコードとのいくつかの違いを示しています。 まず、 Appを拡張したり、メインメソッドを定義したりするオブジェクトでコードをラップする必要はありません。 次に、 println や変数の初期化など、通常はトップレベルでは許可されないステートメントを記述できます。
「監視モード」もあります。このモードでは、スクリプトが完了してもランタイムは終了しませんが、代わりに、ファイルが変更されるたびに実行されます。 このモードを有効にするには、ammコマンドで-wフラグを指定します: amm -wSimpleScript.sc。
3.2. インポートとライブラリ
Ammoniteを使用すると、スクリプトを複数の小さなスクリプトに分割できます。 繰り返しますが、プロジェクトを設定したり、ソースディレクトリを設定したりしなくても、これを行うことができます。
// Constants.sc
val aValue = 5
val anotherValue = 10
// Imports.sc
import $file.Constants, Constants._
println(s"Imported value: $aValue")
この場合、Constants.scとImports.scの2つのスクリプトを定義しました。 前者は、後で import $file.Constantsを介して後者によってインポートされるいくつかの値を定義します。 Import.sc は、Constants.scで定義された任意の値を自由に使用できます。 例を実行してみましょう:
$ amm Imports.sc
Imported value: 5
他のAmmoniteスクリプトに加えて、外部のIvyおよびMavenライブラリをインポートすることもできます。 たとえば、 Playライブラリをインポートするには、 import $ ivy.`com.typesafe.play :: play:2.8.8` を使用して、スクリプトで使用できます。 Ammoniteは、スクリプトをコンパイルする前にライブラリをインポートし、ライブラリが見つからない場合はエラーを表示します。
最後に、Ammoniteには、HTTP呼び出しの作成やJSONオブジェクトの処理など、一般的なユースケース用のライブラリがいくつか付属しています。
3.3. 引数
@main としてマークされたメソッドを定義することにより、Ammoniteスクリプトをパラメーター化できます。
// Arguments.sc
@main
def main(name: String, age: Int) = {
println(s"Hello, $name. I'm $age")
}
最初に、すべてのパラメーターを渡して上記の例を実行してみましょう。
$ amm Arguments.sc Baeldung 6
Hello, Baeldung. I'm 6
一方、年齢などの引数を省略すると、エラーが発生します:
$ amm Arguments.sc Baeldung
Missing argument: --age <int>
Expected Signature: main
--name <str>
--age <int>
amm コマンドで名前を指定することによっても引数を渡すことができます: amm Arguments.sc –name Baeldung –age6。
最後に、間違った型の引数を指定すると、エラーも発生します。
$ amm Arguments.sc Baeldung A
Invalid argument --age <int> failed to parse "A" due to java.lang.NumberFormatException: For input string: "A"
Expected Signature: main
--name <str>
--age <int>
// Varargs.sc
@main
def main(fruits: String*) = {
fruits foreach {println(_)}
}
それを実行してみましょう:
$ amm Varargs.sc banana orange apple
banana
orange
apple
varargs を使用する場合、 –fruitを介して引数の名前を指定することはできません。 そうしようとすると、 –fruitはvarargs自体の一部と見なされます。
$ amm Varargs.sc --fruits banana orange apple
--fruits
banana
orange
apple
3.4. 複数の@mainメソッド
@main メソッドを使用すると、スクリプトに引数を渡すことができます。 Ammoniteでは、複数の@mainメソッドを定義できます。 この場合、最初の引数を使用して、実行するメソッドを決定します。 たとえば、2つの @mainメソッドfunA()と funB ()を持つスクリプトを考えてみましょう。
// MultipleMains.sc
@main
def funA(arg: String): Unit = {
println(s"""funA called with param "$arg"""")
}
@main
def funB(n: Int): Unit = {
println(s"""funB called with param "$n"""")
}
いつものように実行してみましょう:
$ amm MultipleMains.sc funA "Test string"
funA called with param "Test string"
$ amm MultipleMains.sc funB 15
funB called with param "15"
Ammoniteは、パラメーターとして渡すときに引用符を使用しているため、引数「テスト文字列」のスペースを認識できることに注意してください。
最後に、 @mainメソッドの名前を指定しない場合、Ammoniteはエラーを返します。
$ amm MultipleMains.sc
Need to specify a sub command: funA, funB
3.5. ファイルの操作
Ammoniteスクリプトを使用すると、標準のScalaプログラムと同様にファイルシステムを操作することもできます。 たとえば、scala.ioのSourceを使用して、ファイルを読み取り、その行数を出力できます。
// FileSystem.sc
import scala.io.Source
@main
def countLines(path: String): Unit = {
val src = Source.fromFile(path)
println(src.getLines.size)
src.close()
}
例を実行して、引数としてスクリプト自体へのパスを渡します。
$ amm FileSystem.sc ./FileSystem.sc
10
一方、存在しないファイルへのパスを渡すと、FileNotFoundExceptionが発生します。
この例は、Ammoniteの相対パスが、スクリプトが実行されている作業ディレクトリから始まることも示しています。
3.6. スクリプトドキュメント
@argアノテーションを使用して、スクリプトの動作と引数の意味をドキュメントに追加できます:
// Doc.sc
@arg(doc = "Sum two numbers and print out the result")
@main
def add(
@arg(doc = "The first number")
n1: Int,
@arg(doc = "The second number")
n2: Int
): Unit = {
println(s"$n1 + $n2 = ${n1 + n2}")
}
この場合、パラメータを指定しないと、Ammoniteはパラメータの説明を表示します。
$ amm Doc.sc
Missing arguments: --n1 <int> --n2 <int>
Expected Signature: add
--n1 <int> The first number
--n2 <int> The second number
4. 結論
この記事では、アンモナイトスクリプトの基本について説明しました。 AmmoniteベースのScalaスクリプトを使用すると、探索的なコードをすばやく作成する方法が必要な場合に、重量のあるプロジェクトを作成する必要がなくなります。
いつものように、コードはGitHubでから入手できます。