Scala でライフゲームを書いてみた
2〜3時間で軽く書いてみました。
初めて Scala をガッツリ書いたのであまり Scala らしさが感じられないコードですが…。
[コード]
import scala.sys.process._ object Lifegame { type Table = List[List[Boolean]] val o = false val x = true def width(table: Table) = { table(0).size } def height(table: Table) = { table.size } def at_cell(table: Table, x: Int, y: Int) = { if(x < 0 || width(table) <= x) o else if(y < 0 || height(table) <= y) o else table(y)(x) } def dead_or_alive(table: Table, x: Int, y: Int) = { val cell = at_cell(table, x, y) val count = List( at_cell(table, x - 1, y - 1), at_cell(table, x - 0, y - 1), at_cell(table, x + 1, y - 1), at_cell(table, x + 1, y - 0), at_cell(table, x + 1, y + 1), at_cell(table, x - 0, y + 1), at_cell(table, x - 1, y + 1), at_cell(table, x - 1, y - 0) ).count(x => x) if(cell) count == 3 || count == 2 else count == 3 } def update(table: Table) = { table.zipWithIndex.map { case(row, y) => row.zipWithIndex.map { case(cell, x) => dead_or_alive(table, x, y) } } } def print(table: Table) = { Process("clear").run println(Range(0, width(table) + 2).map(x => "-").mkString) println(table.map(row => "|" + row.map(x => if(x) "*" else " " ).mkString).mkString("|\n") + "|") println(Range(0, width(table) + 2).map(x => "-").mkString) Console.flush() } def run(table: Table):Int = { print(table) Thread.sleep(500) run(update(table)) return 0 } def main(args: Array[String]) { val table = List( List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, x, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, x, o, x, x, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, x, o, x, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, x, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, x, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, x, o, x, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o), List(o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o) ) run(table) } }
[所感]
要所要所でつまずいたものの、全体的にみればそこまで難しくなかったかなーという感じです。
まあ元々 C++ の実装をそのまま Scala で書きなおしただけですしね(なので Scala らしさがあまりないとも言える
型に関しては C++ よりも厳しいという印象を受けました。
本当は List[List[Boolean]]
という型に依存したくなかったんですが、上手く消すことが出来なかった…。
ダックタイピングするだけでも結構いろいろと記述必要があるぽいのがちょっと厳しい…(良し悪しは別として。
Scala は他にはパターンマッチや trait、Structural Subtyping、implicit あたりなど面白い機能があるぽいので、次 Scala を書くときはそのあたりを少し意識してかいてみたい。
[気になったところ]
C++ でライフゲームを書いてみた
特に意味はないんですが、C++ でライフゲームを書いてみました。
C++14 で動作します(Windows での動作は未確認。
[コード]
#include <cstdlib> #include <thread> #include <string> #include <iostream> #include <vector> int main(){ using table_t = std::vector<std::vector<bool>>; table_t table = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; auto width = [](auto const& table){ return table[0].size(); }; auto height = [](auto const& table){ return table.size(); }; auto at_cell = [&](auto const& table, auto x, auto y){ return x < 0 || width(table) <= x ? false : y < 0 || height(table) <= y ? false : table[y][x]; }; auto dead_or_alive = [&](auto table, auto x, auto y){ auto cell = at_cell(table, x, y); auto count = at_cell(table, x - 1, y - 1) + at_cell(table, x - 0, y - 1) + at_cell(table, x + 1, y - 1) + at_cell(table, x + 1, y - 0) + at_cell(table, x + 1, y + 1) + at_cell(table, x - 0, y + 1) + at_cell(table, x - 1, y + 1) + at_cell(table, x - 1, y - 0); return cell ? count == 3 || count == 2 : count == 3; }; auto update = [&](auto const& table){ table_t result = table; for(int y = 0 ; y < table.size() ; ++y){ for(int x = 0 ; x < table[y].size() ; ++x){ result[y][x] = dead_or_alive(table, x, y); } } return result; }; auto print = [&](auto const& table){ std::system("clear"); // or // std::system("cls"); std::cout << std::string(width(table) + 2, '-') << "\n"; for(auto&& row : table){ std::cout << "|"; for(auto&& cell : row){ std::cout << (cell ? "*" : " "); } std::cout << "|\n"; } std::cout << std::string(width(table) + 2, '-') << "\n"; std::cout << std::endl; }; while(1){ print(table); table = update(table); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } return 0; }
[動作]
C++ だと実装するのが難しいと思いましたが、実際に書いてみるとそこまで複雑なコードにはならなかった。
上のコードだと100行もありませんし、短くしようと思えばもっと短くできそう。
鬼門なのは『接するセル』をどうやって走査するかですかね。
今回は愚直に実装しましたが、もっとスマートなやり方はありそう。
とりあえず、標準ライブラリに Range がほしい。
Ruby の (1..5) と (1...5) の違い
前者は使っていたけど、後者は知らなかかったので覚書。
範囲演算子 ..
を使用した場合は終端を含み、...
を使用した場合は終端を含みません。
p (1..5).to_a # => [1, 2, 3, 4, 5] p (1...5).to_a # => [1, 2, 3, 4]
C++14(17) で zip 書いてみた
現状の C++ で zip を書いたらどうなるんだろう、と思って書いてみました。
[コード]
#include <iostream> #include <algorithm> #include <vector> #include <tuple> template<typename T, typename U> constexpr auto zip_impl(T v1, U v2){ auto vi1 = std::begin(v1); auto vi2 = std::begin(v2); using value_t = decltype(std::make_tuple(*vi1, *vi2)); std::vector<value_t> result{}; while(vi1 != std::end(v1) && vi2 != std::end(v2)){ result.emplace_back(*vi1, *vi2); vi1++; vi2++; } return result; } template<typename T, typename U> constexpr auto zip(T v1, U v2){ return zip_impl(v1, v2); } template<typename T, typename U> constexpr auto zip(std::initializer_list<T> v1, std::initializer_list<U> v2){ return zip_impl(v1, v2); } int main(){ for(auto [x, y] : zip({1, 2, 3, 4, 5}, {'a', 'b', 'c', 'd', 'e'})){ std::cout << x << ":" << y << std::endl; } return 0; }
[出力]
1:a 2:b 3:c 4:d 5:e
https://wandbox.org/permlink/qQEvmwNMjgVt3GHJ
手癖で constexpr
をつけているけど特に constexpr
ではない(std::vector
に依存してるので constexpr
には出来ない…。
使う側では C++17 の『構造化束縛』を使っているけど、実装側は C++14 でも動作すると思う。
っていうか、range based-for で構造化束縛使うのかなり強力なのではないだろうか???
実装は単純に2つのコンテナを iterator で回しながらタプルで保持するようにしてるだけですね。
特に複雑なことはしていないと思う。
最適解かどうかは別としてこういうコードが C++ でも雑に書けるようになったのはだいぶよさがあるなあ。
Ruby の () の戻り値
Ruby だと () の戻り値は nil
になるらしい。
p () # => nil p ().nil? # true
ほぼ使うことはないだろうけど知らなかった(そもそも ()
だけ評価出来ると思ってなかった。
ちなみに #call
が呼び出されるわけではない。
class X def call "call" end def func p self.() # => "call" p () # => nil end end X.new.func
C++17 のクラステンプレートのテンプレート引数推論を使ってみて
C++17 では『クラステンプレートのテンプレート引数推論』という機能が新しく追加されました。
どういう機能かというと C++17 以前では以下のようなクラステンプレートを使用する場合はテンプレート引数を明示化する必要がありました。
template<typename T> struct A{ A(T t) : t{} }; // テンプレート引数を渡して値を定義 auto a = A<int>{42};
しかし、C++17 ではこれを推論してくれるようになりました。
template<typename T> struct A{ A(T t) : t{} }; // コンストラクタの引数から T 型を推論してくれる auto a = A{42};
これによりクラステンプレートを生成する make_xxx
みたいなヘルパ関数を大幅に削減する事が出来そうですね。
上記はコンストラクタから推論していますが、他にも『推定ガイド(deduction guide)』という構文を使用しても推論する事が出来ます。
[Clang 5.0(svn306440) では一部動作しなかった]
本題というか単に Clang 5.0(svn306440) だと動作しなかった、という話ですが。
Clang 5.0(svn306440) では次のようにクラステンプレートのインナークラスだとコンパイルエラーになりました。
template<typename F> struct X{ template<class T> struct A { A(T&& t) : value(t){} T value; }; }; // error: no viable constructor or deduction guide for deduction of template arguments of 'A' auto a = X<int>::A{42};
https://wandbox.org/permlink/ylwbjUX5xs6JFbfn
X
がクラステンプレートでなかったり、GCC 8.0 では問題なかったので Clang のバグかなぁ…。
[参照]
C++ で Non-static data member initializers に auto が使えないのがつらい
C++14(17) になっても Non-static data member initializers に auto
が使えないのがつらい、という話です。
[Non-static data member initializers とは]
Non-static data member initializers とは C++11 から追加された言語機能の一つで クラスのメンバ変数を定義する時に初期値を設定できる という機能です。
C++03 では以下のようにコンストラクタでメンバ変数の初期値を設定することが出来ました。
struct X{ // コンストラクタ時に初期値を設定する X() : value(0) , value2(3.14f){} int value; float value2; };
一方、C++11 以降ではメンバ変数定義時に直截値を代入することが出来ます。
struct X{ // メンバ変数に対して直接初期値を代入する事が出来る // コンストラクタを定義する必要がない int value = 0; float value2 = 3.14f; };
これによりコンストラクタを定義する事なく、メンバ変数の初期値を設定する事が出来ます。
[Non-static data member initializers で auto
を使うことが出来ない]
さて、今の時代、auto
を使って型推論を行う時代ですね。
C++14 では戻り値型を auto
で型推論してくれたり、ラムダ式の引数に auto
を使うことで多相ラムダを実現することが出来るようになりました。
しかし、残念なことに Non-static data member initializers では auto
を使うことが出来ません。
struct X{ // 変数を定義する時に auto を使うのは至極当然…だが… // error: 'auto' not allowed in non-static struct member auto value = 0; auto value2 = 3.14f; };
なんで C++14 では戻り値型に auto
を指定したり、ラムダ式の引数にさえ auto
で型推論してくれるようになったのに!!
Non-static data member initializers では!!
auto
が!!!!
使えない!!!!!
なぜだ!!!!
なぜだ!!!!!!!!!
なぜ auto
が使えないのか理解に苦しむ
[Non-static data member initializers で auto
を使えないと何が困るのか]
さて、先ほどの例だけだと『別に型推論しなくたって普通に型を定義すればいいじゃん』と思うでしょう。 では、次の例だとどうでしょう。
struct X{ // ラムダ式をメンバ変数で保持したいよね!!! auto twice = [](int a){ return a + a; }; };
はいーラムダ式ーきたー 。
ラムダ式は定義された時に『ユニークな型』となるため、decltype()
のようなものを使っても定義時に型を指定することが出来ません。
[キャプチャしてないラムダ式を関数ポインタ型にキャストする]
と、ここで C++ に精通している方ならすでに気づいていると思いますが『キャプチャしてないラムダ式は関数ポインタ型にキャストすること』が出来ます。
ですので、上記の場合は以下のように記述することが出来ます。
struct X{ // キャプチャしてないラムダ式は関数ポインタ型にキャストされる!! std::common_type<int(*)(int)>::type twice = [](int a){ return a + a; }; };
やったね!!! と、思いますがこれもまだ不完全です。
[キャプチャしたラムダ式を std::function
で保持する]
例えば『ラムダ式内で this
を参照したい』場合には this
をキャプチャする必要があるので関数ポインタ型にキャストすることが出来ません。
struct X{ // this をキャプチャしたいんだけどなー… // error: no viable conversion from 'X::(lambda at ...)' to 'std::common_type<int (*)(int)>::type' (aka 'int (*)(int)') std::common_type<int(*)(int)>::type twice = [this](int a){ return a + a + offset; }; int offset = 42; };
なるほど???
が、これも std::function
を使うことで回避することが出来ます。
#include <functional> struct X{ // こういう時の std::function std::function<int(int)> twice = [this](int a){ return a + a + offset; }; int offset = 42; };
std::function
を使うことで多少オーバーヘッドが生じますがまあしょうがないですね。
[多相ラムダの場合]
さて、今までの回避方法はC++11 では有効な手段でした。
しかし、時代は C++14、もうすぐ C++17 もやってきます。
そう、多相ラムダです!!!
今の時代、ラムダ式の引数も型推論を行いたいですよね???
struct X{ // 関数ポインタ型にキャストされるけど、int 型で固定されてしまう… std::common_type<int(*)(int)>::type twice = [](auto a){ return a + a; }; };
本来は多相ラムダとして定義して twice(42)
や twice(3.14)
、twice(std::string("homu"))
など引数型に依存しないように使いたいですよね?
しかし、上記の場合では『関数ポインタ型を決定する時に引数型を決定する必要がある』ので型推論を行うことが出来ません。
この問題は std::function
を使っても同様です。
[キャプチャを行わない場合の回避方法]
回避方法としては一度、メンバ変数以外で定義してからメンバ変数に代入するということは出来ます。
struct X{ // 一度、メンバ変数以外で定義して static constexpr auto twice_ = [](auto self, auto it) constexpr{ return it + it; }; // その変数をメンバ変数として代入する decltype(X::twice_) twice = X::twice_; };
しかし、この場合は this
をキャプチャすることが出来ないのであまりメンバ変数として保持する意味がありません…。
[メンバ関数テンプレートを使う]
そもそも『メンバ変数ではなくてメンバ関数テンプレートとして定義すればいいのでは?』と思う人が多いと思います。
全く持ってそのとおりで通常は素直にメンバ関数テンプレートを定義すれば解決します。
struct X{ // 余計なメンバ変数も定義されないし、this も参照できるし完璧 template<typename T> constexpr auto twice(T t) const{ return t + t + offset; } int offset = 42; };
[メンバ関数テンプレートをオブジェクトとして扱いたかった]
さて、本題というかやりたかったことですが、単純に『メンバ関数テンプレートをオブジェクトとして扱いたかった』からです。
例えば、次のようなことは本来 C++ では行うことは出来ません。
struct X{ template<typename T> constexpr auto twice(T t) const{ return t + t + offset; } int offset = 42; }; X x{}; // x.twice をオブジェクトとして扱いたかったが… // error: reference to non-static member function must be called auto x_twice = x.twice; // x.twice を呼び出す x_twice(42);
上記のように『メンバ関数をオブジェクトとして扱う』場合には関数を『メンバ関数として定義する』のは不向きです。
そしてこれは『メンバ関数をラムダ式として定義する』ことで実現する事が出来ます。
struct X{ // 本来は auto で定義したい… // auto twice = [this](auto it){ std::function<int(int)> twice = [this](auto it){ return it + it + this->offset; }; int offset = 42; }; X x{}; std::cout << x.twice(42) << std::endl; // x.twice をオブジェクトとして扱える auto x_twice = x.twice; // x.twice を呼び出す std::cout << x_twice(12) << std::endl;
このようなことを実現したい場合は Non-static data member initializers で auto
を使うしかないのです…。
[まとめ]
C++14 や C++17 ではあちこちで型推論ができようになったのに Non-static data member initializers で auto
が使えない仕様は控えめにいってクソだと思いました。