以前はFanという名前だったけど、いつの間にかFantomという名前に改名したらしい。オブジェクト指向の静的型付け言語で、C#/Javaっぽい構文だけど、動的型言語的な機能も取り入れてるのがウリのようだ。
で、とりあえずHello, World的なものを書いてみるかーと思ったんだけど、それだとつまらないので、それなりに意味のあるサンプルプログラム何か無いかなーと思っていたのだが、ついさっき解いた問題が丁度いい規模だということに気付いたので、早速Fantomに移植してみた。基本的な文法に関しては、C#/Javaにかなり似ているので、Java/Scalaな人である自分としては割ととっつきやすいと感じた。
class Point { new make(Int x, Int y) { this.x = x; this.y = y; } const Int x const Int y override Bool equals(Obj? that) { if(that == null) return false return x == that->x && y == that->y } } class Maze { static Point? findStart(Str[] maze) { for(i := 0; i < maze.size; i++) { for(j := 0; j < maze[0].size; j++) { if(maze[i][j] == 'S') return Point(j, i) } } return null } static Void main() { input := File.make(`input.txt`).open("r") maze := input.readAllLines() h := maze.size w := maze[0].size q := Point[][,] d := [[1, 0], [-1, 0], [0, 1], [0, -1]] Bool[][] v := List.make(Bool[]#, h) for(y := 0; y < h; y++) { v.add(Bool[,]) for(x := 0; x < w; x++) { v[y].add(false) } } s := findStart(maze) if(s == null) { echo("start not found") return } q.add([s]) Point[]? path := null while(true) { path = q.removeAt(0) p := path.last if(maze[p.y][p.x] == 'G') break if((!v[p.y][p.x]) && maze[p.y][p.x] != '*') { v[p.y][p.x] = true d.each |Int[] i| { q.add(path.dup.add(Point(p.x + i[0], p.y + i[1]))) } } } for(y := 0; y < h; y++) { for(x := 0; x < w; x++) { e := maze[y][x] if(e == 'S' || e == 'G' || !path.contains(Point(x, y))) { Sys.out.print(e.toChar) } else { Sys.out.print('$'.toChar) } } echo("") } } }
以下、適当に書いていたわかったことや思ったことを書き連ねていく。
Javaとほとんど同じ感覚で書くことができる。特にfor文がJavaとほぼ同じものがサポートされているのは大きいかも。
- 変数の宣言と同時に初期化を行うには
TypeName varName := expr
型宣言は省略してもOKらしい。
- メソッド呼び出しのシンタックスはJavaとほぼ同じ
ただし、無引数のメソッドは()を省略できるみたい
- TypeName? でnullを受け入れる型を表現できる
Niceを思い出す機能だが、どうも静的なチェックはちゃんとされないっぽい?
- Listのリテラルは
[e1, e2, ...]
空のリストリテラルは[,]
とすれば良いらしい。Listの要素型は処理系が推論してくれるらしい。
- Listの要素にはlst[index]で添え字を指定してアクセス可能。
- obj->method(arg,...)で動的なメソッド呼び出しが可能
静的な型を無視してメソッド呼び出しがしたい場合に便利
- メソッド定義の文法はJavaとほぼ同じ
staticなどもある。
- 型名は大文字で始める規約?
- メソッドをオーバーライドするときは、override修飾子が必要
無いとコンパイルエラーになる。この辺はC#やScalaと同じ。
- constを付けることで変更不能な変数を作れる
|TypeName varName -> ReturnType| { .. }
で無名関数を定義できる
->ReturnType
を省略したときは、Voidを返すものとみなされるらしい。