設計設計設計、すこし実装

とりあえずいきなり作ろうとしているゲーム全体の作成に着手したら、細かい実装上の「あれってどうするんだ?」に気をとられて全体の設計が疎かになると思ったのでゲームは3目並べにした。ゲームがなんであっても再利用できるようなライブラリが作りたいわけだから正しいはず。

typeとdataとnewtypeとclassとinstance。モナドよりこっちの方が重要な気がするぞ!

HaskellのclassはJavaのinterfaceだ」という理解で7割くらいは正しいと思う。ゲームのデータを持つオブジェクトは、以前Pythonで殴りがいたときにはただのdictだったのだけど、Haskellでしっかり型を宣言しようと思うとそんなぬるい事は許されない。というわけでとりあえずJava脳でGame interface(Java語)を作ってみた。として着手を意味するPlayってinterface(Java語)も作ってみた。特に中に入れるべきメソッドが思いつかなかったのでとりあえずどっちも文字列化できて欲しいよね、とto_strってメソッドを両方に宣言する。Multiple declarations of `Main.to_str'と怒られる。

そう、これが「HaskellのclassはJavaのinterfaceだ」という理解の正しくないポイントなんだよな。Javaのinterfaceで宣言したメソッドの名前のスコープはインスタンス単位だ。でもHaskellの方はそうじゃない。トップレベルスコープの関数だ。

もう一つ、Javaだとクラスやインスタンスの中でメソッドとかいう関数のようなものを定義すると、なんかオブジェクトがthisって名前に束縛されてから呼ばれるわけじゃない?an_obj.a_method(an_arg)って呼ぶとa_methodの中でthisっていう決め打ちの変数にアクセスすることでan_objにアクセスできる。Pythonの場合、an_obj.a_method(an_arg)って呼ぶと、a_methodの第一引数にan_objが渡される。Haskellはさらに、a_methodがそもそもan_objと無関係にトップレベルの関数なので、an_objは引数で渡してやる必要がある。

// Java風
class Foo {
  int a_method(int an_arg){
    ... (なぜかthisが使える)
  }
}

Foo an_obj = new Foo();
an_obj.a_method(an_arg);
# Python風
class Foo:
  def a_method(self, an_arg):
    ... (selfが使える)

an_obj = Foo()
an_obj.a_method(an_arg)
-- Haskell風
a_method :: (Foo a) => a -> Int -> Int
a_method an_obj an_arg = ... (もちろんan_objが使える)

an_obj = MakeFoo ...
result = a_method an_obj 1

Haskellのケースを「メソッド」って呼ぶのは変。だってclass Fooに属しているわけではないのだもの。Fooの型の引数を第一引数に渡せることが保証されているだけ。

第一引数である必然性は全くないのだけど少しうれしい。

# Python
f = an_obj.a_method
f(an_arg) # ちゃんとa_method(an_obj, an_arg)になる
-- Haskell
f = a_method an_obj
f an_arg -- ちゃんと a_method an_obj an_argになる

などということを考えたり設計したり、入れ物を作ったりで、中身はまだ3目並べの表示しかやってない。でさ、結局to_strは「Gameのto_str」「Playのto_str」というもんじゃなくて「トップレベルのto_str」なんだから、それはGameとかPlayってクラスじゃなくて「表示できる」ってクラスになるべきだよね。そしてそれはShowクラスとshowだ。

--「クラス Game のインスタンスであるような型 a のそれぞれに対して、
-- possible_hands は (Play b) => a -> [b] という型をもつ」
class Game a where
    -- ゲームの局面を受け取って選択可能な手のリストを返す
    possible_hands :: (Play b) => a -> [b]
    -- ゲームと着手を受け取って、更新した新しいゲームを返す
    step_game :: (Play b) => a -> b -> a
    -- ゲームを受け取ってゲームが終わったかどうかと、誰が勝ったのかを返す
    check_is_finished :: a -> (Bool, Int)

-- T3 := Tic Tac Toe    
newtype T3Game = MakeT3Game {value :: [Int]}

instance Game T3Game

-- 「 T3Game 型はクラス Show のインスタンスで、ここに show を定義する。」
instance Show T3Game where 
    show xs = unlines $ map unwords $ (map (\f -> f cs) [(take 3), (take 3.drop 3), (take 3.drop 6)]) 
        where cs = map int2cell $ value xs

int2cell 0 = "."
int2cell 1 = "x"
int2cell 2 = "o"           


class Play a

class Player a where
    -- ゲームと着手可能な選択肢を受け取って一つ選択する
    think :: (Game b, Play c) => a -> b -> [c] -> c
    

main = putStr $ show $ MakeT3Game [0,0,0,0,0,1,0,0,2]