少年易酔學難成

IT/技術的な話題について書きます

プログラミング言語Flixに入門しようとしたけど敗北した

概要

この記事は株式会社アットウェアのアドベントカレンダー10日目の記事です(間に合いませんでした...)

朝、log4jの話題で盛り上がるまでは仕事SlackでFlixが話題になってて、今日昨日のアドベントカレンダーのネタにも困ってたので、簡単なコードを書いて記事にしようと考えました。 しかし、if文を書くにも色々ハマってしまい、結局Hello Worldくらいしかできませんでした、という記事です。

Flixとは

主にオーフス大学、ウォータールー大学によって開発されている、関数型・論理型プログラミング言語らしいです。 ぱっと見はScalaからオブジェクト指向成分を抜いた感じに見えます。1

以下の公式サイトをみれば、雰囲気だけは何となくわかるかもしれません。 https://flix.dev/

環境構築

環境構築はVSCodeflixプラグインを使って行うのが簡単です。 以下の通りに行ってください。

  1. flixプラグインVSCodeにインストールします
  2. サンプルプロジェクト用のフォルダを作ってVSCodeで開きます
  3. コマンドパレットからFlix Package Manager: initを実行します。

すると、2で作ったフォルダにプロジェクトの雛形が展開されます。 src/Main.flixには既にHello Worldするコードが生成されていますね。

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

main関数の戻り値がInt32 & Impureとなっています。 なぜこうした型になるのかはわかりませんが、副作用が型に現れているという理解で良いんでしょうか。 ここらへん時間なくてドキュメントを全く読めてないので、後日リベンジまでに読み込んでおきたいところです。

このコードはコマンドパレットでFlix Package Manager: runを選択すれば実行できます。

$ java -jar '/Users/tsatow/Library/Application Support/Code/User/globalStorage/flix.flix/flix.jar' run 
Hello World!                                                                    
Main exited with status code 0.

↑を見ていて気づくと思いますが、Flix Package Manager: runは単にflix.jarを呼び出しているだけです。

これがどんな機能をもっているかは公式ドキュメントに書いてあります。
このjarは公式サイトにもリンクがなくどこからダウンロードすればいいのか良くわからなかったですが、とりあえずリポジトリをcloneしてgradleでビルドしてやれば手に入ります。 初回のビルドは自分のPCで約10分程度でした。

簡単なプログラムを書く

さて、さすがにHello Worldしただけで1記事書くのは厳しいので、もう少しだけ複雑なプログラムを書きます。 ちょうど詳解Rustプログラミングを読んでいたので、その中に出てきたgrep-liteをFlixで実装しようと思います。

grep-liteは、grep-lite <pattern> <file>という形式で呼び出し、inputで指定されたファイルを読み込んで正規表現でpatternにマッチした行を表示するものとします。

とりあえず、patternとファイルが指定されなかった時は標準エラー出力に使い方を表示するところまで書いてみます。

def main(args: Array[String]): Int32 & Impure = {
  if (args.length != 2) {
    Console/StdErr.printLine("USAGE:\n    grep-lite <pattern> <input>\nARGS:    <pattern>    The pattern to search for\n    <input>      File to search");
    1
  } else {
    Console.printLine("");
    0
  }
}

標準エラー出力にはちょっと手こずりました。
Console.StdErrを使うべきだということはドキュメントからすぐわかったのですが、Console.StdErr.printLine("...");と呼び出すものと思っていて、なかなかエラーを解消できず苦労しました。2 改めてConsoleを打ち直して注意深くVSCodeのサジェストを観察して、ようやくConsole/StdErr.printLine("...");と書くべきだったことに気づきました。 こういったことも現時点ではまだドキュメント化されていません。(多分)

あと、if式も書き方がわからなくて、↓のflixリポジトリのコードをみたんですけど、複数式でも{...}が無いように見えたので、オフサイドルールでもあるのかなと思ってしまいました。 でもこれ多分、HaskellとかOCamllet ... inみたいなやつで一つの式なんですかね。手元で書く限りは、Flixでは複数の式を書くときは{...}で囲む必要がありそうでした。

        else if (nonValue(e1))
            // An application where the first argument is *NOT* a value.
            // Reduction should continue in the first argument.
            let (rdx, ec) = redex(e1);
                (rdx, EApp1(ec, e2))

さて、次はファイルを読み込んで1行ずつ正規表現でマッチさせて...と考えて標準ライブラリのドキュメントを読んでいたら、正規表現用のライブラリがまだ実装されていないことに気が付きました。 さすがにどこかで使ってるだろうとソースコードを検索したら、どうやらJavaのライブラリを使っているようです。

ならばと、ここの書きっぷりを真似てみると...

def main(args: Array[String]): Int32 & Impure = {
  import static java.util.regex.Pattern.compile(String); // Error!
  if (args.length != 2) {
    Console/StdErr.printLine("USAGE:\n    grep-lite <pattern> [input]\nARGS:    <pattern>    The pattern to search for\n    <input>      File to search");
    1
  } else {
    Console.printLine("");
    0
  }
}

なぜかエラーが出ます。

Invalid input "static j", expected '=', Constructor, Method, StaticMethod, GetField, PutField, GetStaticField, PutStaticField, ':', "->", '[', '.', "::" or ":::" (line 2, column 10):
  import static java.util.regex.Pattern.compile(String);
         ^

今日はここまで来たところで心が折れたので撤退することにしました。

一応、String.flixには↓のような実装のisMatch関数もあるんですけど、この用途で使うのはちょっと気が引けました。

    pub def isMatch(regex: {regex :: String}, s: String): Bool =
        try {
            import java.lang.String.matches(String);
            matches(s, regex.regex) as & Pure
        } catch {
            case _: ##java.util.regex.PatternSyntaxException => false
        }

感想

  • まだ使ってる人もドキュメントも少ない新しい言語の入門はなかなかハードでした
  • Scalaに似てるから余裕だろうと舐めてギリギリにブログ書き始めた挙げ句アドベントカレンダーの10日目に遅刻して、本当に申し訳ありませんでした!
  • 次回までにもう少しドキュメントを読んでリベンジします。

  1. 実際、この記事のFlixのコードはScalaシンタックスハイライトを入れていますが、それなりに効いています。

  2. .を入力しても補完が出なくてバグを疑ってしまいました