Flixをインストールしてテストを実行する

はじめに

最近所属する会社内でFlixというJVM系の言語が新しく出たみたいなのを見かけて面白そうだったので少し触ってみたいと思います。 色々深堀りをできそうなポイントはあるのですが一旦深く突っ込まず個々ではインストールして動かすことと、簡単に概要を把握することに努めます。

Flixとは

オーフス大学ウォータールー大学が主体となって開発されている言語で、様々な言語からインスパイヤーされています。
公式サイトには以下のように記述されています。

Flix is inspired by OCaml and Haskell with ideas from Rust and Scala. Flix looks like Scala, but its type system is based on Hindley-Milner. Two unique features of Flix are its polymorphic effect system and its support for first-class Datalog constraints. Flix compiles to efficient JVM bytecode, runs on the Java Virtual Machine, and supports full tail call elimination.

FlixはOCamlHaskellの影響を受けており、RustやSlacaのアイディアなども取り入れているみたいですね。見た目はScalaですが、型システムはHindley-Milner型らしいです。
Hindley-Milnerは型推論アルゴリズムの一種であるようですが、ここではあまり主題ではないため深堀はしません。
その他にも公式サイトのWhyによればGoのチャンネル通信ベースの非同期やElmのようなextensible records、あとはユニークな機能としてpolymorphic effect system、purity polymorphic functions、first-class Datalog constraints(こいつらについても別のブログでまとめようかとは思いますが、ここでは深くふれません)というものがあるらしいです。
JVM系の言語なのでJavaバイトコードコンパイルされJVM上で動きます。

動かしてみる

動作環境

動作環境は以下とおりです。

$ uname -srvmpio
Linux 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:    20.04
Codename:   focal

$ java --version
openjdk 17 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)

Flixのバージョンなどはインストール時に明記します。

インストール

とりあえずFlixを気軽に動かしてみるには、プレイグラウンドを使って見る方法が一番簡単にできると思います。
また、VS Codoプラグインも用意されているようでそちらをインストールするのが現段階ではコーディングをゴリゴリやっていく上では良い方法かと思います。
ただ、このブログでは、Flixのjarをダウンロードして実際に手動でテストの実行やコンパイルを行います。 flix.jarはGitHubのリリースページのところから入手できます。
今回は現状の最新である0.25.0をダウンロードしてきて、適当なディレクトリに配置します。 自分の場合は/opt/flixディレクトリを作成しそこにおきました。

ビルド&パッケージマネージャー

公式のBuild and Package Managementによると現状(2022/01/02)ではまだ、依存解決などの方法は提供されていないようですが、ビルドしてパッケージをシェアすること自体は可能なようです。
セントラルなパッケージのリポジトリなどはまだなくバージョンの管理や配布などはマニュアルで行わないと行けないようです。
バージョン管理などの方法については将来的には提供される予定のようです(foo-1.2.1.fpkgという名前のアーティファクト?に含まれる予定のようです)。
現状Flixは、/path/to/flix.jar <command>のような実行方法でコンパイルやテストの実行 ここで、コマンドには以下のようなものがあります。

コマンド 説明
init カレントディレクトリに新しいプロジェクトの作成
check カレントプロジェクトにエラーがないかチェック
build カレントプロジェクトのコンパイル
build-jar カレントプロジェクトのjarファイルの作成
build-pkg カレントプロジェクトのfpkg-fileの作成
run カレントプロジェクトのメイン関数の実行
test カレントプロジェクトのテストの実行

このブログでは一通りのコマンドは試そうと思います。

標準APIの一覧

標準APIの一覧はここから確認できます。

Hello World

インストールと簡単な概要をまとめたところで、先ずはHello Worldを記述して実行してみます。
先ずは、プロジェクトを作成します。

$ mkdir hello-world && cd hello-world

$ java -jar /opt/flix/flix.jar init

$ tree
.
├── HISTORY.md
├── LICENSE.md
├── README.md
├── build
├── lib
├── src
│   └── Main.flix
└── test
    └── TestMain.flix

必要なものができたみたいですね。
見た感じ、src/にflixのコードを追加していって、test/にテストコードを記述するみたいです。
すでにMain.flixと言う名前のファイルが作成されHello Worldのプログラムが作成されています。

// The main entry point.
def main(_args: Array[String]): Int32 & Impure =
  Console.printLine("Hello World!");
  0 // exit code

このPJを実行するとHello Worldの文字列が標準出力に出力されます。

$ java -jar /opt/flix/flix.jar run
Hello World!                                                                    
Main exited with status code 0.

テストの記述

プロジェクトを初期化するとすでに以下のようなテストがtest/TestMain.flixに記述されています。
テストに関してはprinciplesのBuilt-in unit testsの項目で軽くふれられています。

Built-in unit tests
Flix supports unit tests as part of the language. We believe such integration avoids fragmentation of the ecosystem and ultimately leads to better tool support.

ライブラリーなどを使うのでは無くビルトインでテストの方法がサポートされているようですね。

@test
def test01(): Bool = 1 + 1 == 2

1+1の実行結果をアサーションしているテストですね。
@testを付けてBool型を返す関数を記述すれば良いようです。
テストに関して深く説明されたドキュメンテーションが見つからなかったのですが、おそらくMockなどの方法は現時点ではまだサポートされていないのだと思います。

上記のテストを実行すると以下のような結果が得られます。

$ java -jar /opt/flix/flix.jar test
-- Tests -------------------------------------------------- root                

  ✓ test01
  
  Tests Passed! (Passed: 1 / 1)

テストを以下のように書き換えて失敗するようにしてみます。

@test
def test01(): Bool = false

実行すると以下のような結果を得られます。

$ java -jar /opt/flix/flix.jar test
-- Tests -------------------------------------------------- root                

  ✗ test01: Returned false. (test/TestMain.flix:2:5)
  
  Tests Failed! (Passed: 0 / 1)

当たり前ですがテストは失敗しました。

それでは、独自の関数を1つ書いて、それをテストするコードを書いてみます。
関数は、人物名の文字列を受け取って、その文字列を返すだけの簡単なものを想定して作成します。

先ずはテストをTestMain.flix以下のように追記します。

@test
def testGreeting(): Bool = greeting("moheji") == "Hello!! moheji!!"

そして、空の実装の方もMain.flixに作っておきます。

// The main entry point.
def main(_args: Array[String]): Int32 & Impure =
  Console.printLine(greeting("moheji"));
  0 // exit code

def greeting(name: String): String = ???

この状態でテストを実行するとエラーになります。

$ java -jar /opt/flix/flix.jar test
-- Tests -------------------------------------------------- root                

  ✓ test01
  ✗ testGreeting: Hole '?h26182' at src/Main.flix:6:38 (test/TestMain.flix:5:5)
  
  Tests Failed! (Passed: 1 / 2)

それでは実装の方を修正します。

// The main entry point.
def main(_args: Array[String]): Int32 & Impure =
  Console.printLine(greeting("moheji"));
  0 // exit code

def greeting(name: String): String =
  "Hello!!" + " " + name + "!!"

この状態で、テストを実行すると今度は成功します。

$ java -jar /opt/flix/flix.jar test
-- Tests -------------------------------------------------- root                

  ✓ test01
  ✓ testGreeting
  
  Tests Passed! (Passed: 2 / 2)

jarを作成する

flixコマンドを使えばプロジェクトをJarに固めることができます。 実行は以下の用にbuild-jarコマンドで行います。

$ java -jar /opt/flix/flix.jar  build-jar

$ tree -L 1 
.
├── HISTORY.md
├── LICENSE.md
├── README.md
├── build
├── hello-world.jar
├── lib
├── src
├── target
└── test

コマンドを実行すると特にログ出力も無くプロジェクトルートにhello-world.jarが作成されているのがわかります。
このjarを実行してみます。

$ java -jar hello-world.jar 
Hello!! moheji!!

正しく実行を行えているようですね。